Compare commits
25 Commits
test
...
7eee0bd032
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eee0bd032 | ||
|
|
f6b321b602 | ||
|
|
af91842b14 | ||
| 04e238fd66 | |||
|
|
e0c53c97ba | ||
| 09a77c14c9 | |||
|
|
c7533a8deb | ||
|
|
1bab7f642f | ||
|
|
a988bbbc33 | ||
|
|
395d670298 | ||
|
|
7d4242abb1 | ||
|
|
9c7eb5ee4e | ||
|
|
833f4a5a07 | ||
|
|
b9bc121d43 | ||
|
|
b2acb8c922 | ||
| cf4d14a3cb | |||
| 30eb82ba53 | |||
| a2a2c35ab3 | |||
|
|
03f04b2f53 | ||
|
|
55da953f32 | ||
| 684f910090 | |||
| 5feaa9e15c | |||
|
|
04e2549a66 | ||
| fb4f7048ab | |||
|
|
a3f01eca7c |
4
.env
4
.env
@@ -48,6 +48,10 @@ EMAIL_FROM=test@ma-al.com
|
|||||||
EMAIL_FROM_NAME=Gitea Manager
|
EMAIL_FROM_NAME=Gitea Manager
|
||||||
EMAIL_ADMIN=goc_marek@ma-al.pl
|
EMAIL_ADMIN=goc_marek@ma-al.pl
|
||||||
|
|
||||||
|
# STORAGE
|
||||||
|
STORAGE_ROOT=./storage
|
||||||
|
|
||||||
|
|
||||||
I18N_LANGS=en,pl,cs
|
I18N_LANGS=en,pl,cs
|
||||||
|
|
||||||
PDF_SERVER_URL=http://localhost:8000
|
PDF_SERVER_URL=http://localhost:8000
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -5,4 +5,6 @@ bin/
|
|||||||
i18n/*.json
|
i18n/*.json
|
||||||
*_templ.go
|
*_templ.go
|
||||||
tmp/main
|
tmp/main
|
||||||
test.go
|
test.go
|
||||||
|
storage/*
|
||||||
|
!storage/.gitkeep
|
||||||
@@ -2,8 +2,10 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -24,7 +26,8 @@ type Config struct {
|
|||||||
GoogleTranslate GoogleTranslateConfig
|
GoogleTranslate GoogleTranslateConfig
|
||||||
Image ImageConfig
|
Image ImageConfig
|
||||||
Cors CorsConfig
|
Cors CorsConfig
|
||||||
MailiSearch MeiliSearchConfig
|
MeiliSearch MeiliSearchConfig
|
||||||
|
Storage StorageConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type I18n struct {
|
type I18n struct {
|
||||||
@@ -95,6 +98,10 @@ type EmailConfig struct {
|
|||||||
Enabled bool `env:"EMAIL_ENABLED,false"`
|
Enabled bool `env:"EMAIL_ENABLED,false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StorageConfig struct {
|
||||||
|
RootFolder string `env:"STORAGE_ROOT"`
|
||||||
|
}
|
||||||
|
|
||||||
type PdfPrinter struct {
|
type PdfPrinter struct {
|
||||||
ServerUrl string `env:"PDF_SERVER_URL,http://localhost:8000"`
|
ServerUrl string `env:"PDF_SERVER_URL,http://localhost:8000"`
|
||||||
}
|
}
|
||||||
@@ -155,7 +162,7 @@ func load() *Config {
|
|||||||
|
|
||||||
err = loadEnv(&cfg.OAuth.Google)
|
err = loadEnv(&cfg.OAuth.Google)
|
||||||
if err != nil {
|
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)
|
err = loadEnv(&cfg.App)
|
||||||
@@ -170,12 +177,12 @@ func load() *Config {
|
|||||||
|
|
||||||
err = loadEnv(&cfg.I18n)
|
err = loadEnv(&cfg.I18n)
|
||||||
if err != nil {
|
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)
|
err = loadEnv(&cfg.Pdf)
|
||||||
if err != nil {
|
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)
|
err = loadEnv(&cfg.GoogleTranslate)
|
||||||
@@ -185,19 +192,25 @@ func load() *Config {
|
|||||||
|
|
||||||
err = loadEnv(&cfg.Image)
|
err = loadEnv(&cfg.Image)
|
||||||
if err != nil {
|
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)
|
err = loadEnv(&cfg.Cors)
|
||||||
if err != nil {
|
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 {
|
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
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +321,22 @@ func setValue(field reflect.Value, val string, key string) error {
|
|||||||
return nil
|
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) {
|
func parseEnvTag(tag string) (key string, def *string) {
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/authService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/authService"
|
||||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
)
|
)
|
||||||
@@ -60,9 +64,52 @@ func AuthMiddleware() fiber.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set user in context
|
// Create locale. LangID is overwritten by auth Token
|
||||||
c.Locals(constdata.USER_LOCALES_NAME, user.ToSession())
|
var userLocale model.UserLocale
|
||||||
c.Locals(constdata.USER_LOCALES_ID, user.ID)
|
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)
|
||||||
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
@@ -71,21 +118,14 @@ func AuthMiddleware() fiber.Handler {
|
|||||||
// RequireAdmin creates admin-only middleware
|
// RequireAdmin creates admin-only middleware
|
||||||
func RequireAdmin() fiber.Handler {
|
func RequireAdmin() fiber.Handler {
|
||||||
return func(c fiber.Ctx) error {
|
return func(c fiber.Ctx) error {
|
||||||
user := c.Locals("user")
|
originalUserRole, ok := localeExtractor.GetOriginalUserRole(c)
|
||||||
if user == nil {
|
if !ok {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
"error": "not authenticated",
|
"error": "not authenticated",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
userSession, ok := user.(*model.UserSession)
|
if originalUserRole != model.RoleAdmin {
|
||||||
if !ok {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "invalid user session",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if userSession.Role != model.RoleAdmin {
|
|
||||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
||||||
"error": "admin access required",
|
"error": "admin access required",
|
||||||
})
|
})
|
||||||
@@ -95,22 +135,70 @@ func RequireAdmin() fiber.Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserID extracts user ID from context
|
// Webdav
|
||||||
func GetUserID(c fiber.Ctx) uint {
|
func Webdav() fiber.Handler {
|
||||||
userID, ok := c.Locals("userID").(uint)
|
authService := authService.NewAuthService()
|
||||||
if !ok {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return userID
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser extracts user from context
|
return func(c fiber.Ctx) error {
|
||||||
func GetUser(c fiber.Ctx) *model.UserSession {
|
authHeader := c.Get("Authorization")
|
||||||
user, ok := c.Locals("user").(*model.UserSession)
|
if authHeader == "" {
|
||||||
if !ok {
|
c.Set("WWW-Authenticate", `Basic realm="webdav"`)
|
||||||
return nil
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"error": "authorization token required",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(authHeader, "Basic ") {
|
||||||
|
c.Set("WWW-Authenticate", `Basic realm="webdav"`)
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"error": "invalid authorization token",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded := strings.TrimPrefix(authHeader, "Basic ")
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(encoded)
|
||||||
|
if err != nil {
|
||||||
|
c.Set("WWW-Authenticate", `Basic realm="webdav"`)
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"error": "invalid authorization token",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
credentials := strings.SplitN(string(decoded), ":", 2)
|
||||||
|
rawToken := ""
|
||||||
|
if len(credentials) == 1 {
|
||||||
|
rawToken = credentials[0]
|
||||||
|
} else if len(credentials) == 2 {
|
||||||
|
rawToken = credentials[1]
|
||||||
|
}
|
||||||
|
if len(rawToken) != constdata.NBYTES_IN_WEBDAV_TOKEN*2 {
|
||||||
|
c.Set("WWW-Authenticate", `Basic realm="webdav"`)
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"error": "invalid authorization token",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// we identify user based on this token.
|
||||||
|
user, err := authService.GetUserByWebdavToken(rawToken)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"error": "user not found",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.WebdavExpires != nil && user.WebdavExpires.Before(time.Now()) {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"error": "invalid or expired token",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var userLocale model.UserLocale
|
||||||
|
userLocale.OriginalUser = user
|
||||||
|
userLocale.User = user
|
||||||
|
c.Locals(constdata.USER_LOCALE, &userLocale)
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
}
|
}
|
||||||
return user
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfig returns the app config
|
// GetConfig returns the app config
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"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/service/langsService"
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,12 +24,8 @@ func LanguageMiddleware() fiber.Handler {
|
|||||||
if id, err := strconv.ParseUint(langIDStr, 10, 32); err == nil {
|
if id, err := strconv.ParseUint(langIDStr, 10, 32); err == nil {
|
||||||
langID = uint(id)
|
langID = uint(id)
|
||||||
if langID > 0 {
|
if langID > 0 {
|
||||||
lang, err := langService.GetLanguageById(langID)
|
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
|
||||||
if err == nil {
|
return c.Next()
|
||||||
c.Locals("langID", langID)
|
|
||||||
c.Locals("lang", lang)
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,12 +36,8 @@ func LanguageMiddleware() fiber.Handler {
|
|||||||
if id, err := strconv.ParseUint(cookieLang, 10, 32); err == nil {
|
if id, err := strconv.ParseUint(cookieLang, 10, 32); err == nil {
|
||||||
langID = uint(id)
|
langID = uint(id)
|
||||||
if langID > 0 {
|
if langID > 0 {
|
||||||
lang, err := langService.GetLanguageById(langID)
|
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
|
||||||
if err == nil {
|
return c.Next()
|
||||||
c.Locals("langID", langID)
|
|
||||||
c.Locals("lang", lang)
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,8 +51,7 @@ func LanguageMiddleware() fiber.Handler {
|
|||||||
lang, err := langService.GetLanguageByISOCode(isoCode)
|
lang, err := langService.GetLanguageByISOCode(isoCode)
|
||||||
if err == nil && lang != nil {
|
if err == nil && lang != nil {
|
||||||
langID = uint(lang.ID)
|
langID = uint(lang.ID)
|
||||||
c.Locals("langID", langID)
|
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
|
||||||
c.Locals("lang", lang)
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,8 +61,7 @@ func LanguageMiddleware() fiber.Handler {
|
|||||||
defaultLang, err := langService.GetDefaultLanguage()
|
defaultLang, err := langService.GetDefaultLanguage()
|
||||||
if err == nil && defaultLang != nil {
|
if err == nil && defaultLang != nil {
|
||||||
langID = uint(defaultLang.ID)
|
langID = uint(defaultLang.ID)
|
||||||
c.Locals("langID", langID)
|
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
|
||||||
c.Locals("lang", defaultLang)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
@@ -104,11 +96,9 @@ func parseAcceptLanguage(header string) string {
|
|||||||
return strings.ToLower(first)
|
return strings.ToLower(first)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLanguageID extracts language ID from context
|
func returnNewLocale(lang_id uint) *model.UserLocale {
|
||||||
func GetLanguageID(c fiber.Ctx) uint {
|
newLocale := model.UserLocale{}
|
||||||
langID, ok := c.Locals("langID").(uint)
|
newLocale.OriginalUser = &model.Customer{}
|
||||||
if !ok {
|
newLocale.OriginalUser.LangID = lang_id
|
||||||
return 0
|
return &newLocale
|
||||||
}
|
|
||||||
return langID
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,15 +268,15 @@ func (h *AuthHandler) RefreshToken(c fiber.Ctx) error {
|
|||||||
|
|
||||||
// Me returns the current user info
|
// Me returns the current user info
|
||||||
func (h *AuthHandler) Me(c fiber.Ctx) error {
|
func (h *AuthHandler) Me(c fiber.Ctx) error {
|
||||||
user := c.Locals("user")
|
userLocale := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
|
||||||
if user == nil {
|
if userLocale.OriginalUser == nil {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated),
|
"error": responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"user": user,
|
"user": *userLocale.OriginalUser,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,21 +351,12 @@ func (h *AuthHandler) CompleteRegistration(c fiber.Ctx) error {
|
|||||||
|
|
||||||
// Updates JWT Tokens. Requires authentication and updates access token only
|
// Updates JWT Tokens. Requires authentication and updates access token only
|
||||||
func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
|
func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
|
||||||
userLocals, ok := c.Locals(constdata.USER_LOCALES_NAME).(*model.UserSession)
|
userLocale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(fiber.StatusUnauthorized).
|
return c.Status(fiber.StatusUnauthorized).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated)))
|
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
|
// Parse language and country_id from query params
|
||||||
langIDStr := c.Query("lang_id")
|
langIDStr := c.Query("lang_id")
|
||||||
|
|
||||||
@@ -375,7 +366,7 @@ func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
|
|||||||
return c.Status(fiber.StatusBadRequest).
|
return c.Status(fiber.StatusBadRequest).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadLangID)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadLangID)))
|
||||||
}
|
}
|
||||||
user.LangID = uint(parsedID)
|
userLocale.OriginalUser.LangID = uint(parsedID)
|
||||||
}
|
}
|
||||||
|
|
||||||
countryIDStr := c.Query("country_id")
|
countryIDStr := c.Query("country_id")
|
||||||
@@ -386,10 +377,10 @@ func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
|
|||||||
return c.Status(fiber.StatusBadRequest).
|
return c.Status(fiber.StatusBadRequest).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadCountryID)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadCountryID)))
|
||||||
}
|
}
|
||||||
user.CountryID = uint(parsedID)
|
userLocale.OriginalUser.CountryID = uint(parsedID)
|
||||||
}
|
}
|
||||||
|
|
||||||
newAccessToken, err := h.authService.UpdateJWTToken(&user)
|
newAccessToken, err := h.authService.UpdateJWTToken(userLocale.OriginalUser)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
|
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package public
|
|||||||
import (
|
import (
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
|
"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/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/nullable"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
@@ -30,7 +31,7 @@ func RoutingHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *RoutingHandler) GetRouting(c fiber.Ctx) error {
|
func (h *RoutingHandler) GetRouting(c fiber.Ctx) error {
|
||||||
lang_id, ok := c.Locals("langID").(uint)
|
lang_id, ok := localeExtractor.GetLangID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/cartsService"
|
"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/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/nullable"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
@@ -37,7 +38,7 @@ func CartsHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
|
func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
|
||||||
userID, ok := c.Locals("userID").(uint)
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
@@ -53,7 +54,7 @@ func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
|
func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
|
||||||
userID, ok := c.Locals("userID").(uint)
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
@@ -78,7 +79,7 @@ func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *CartsHandler) RetrieveCartsInfo(c fiber.Ctx) error {
|
func (h *CartsHandler) RetrieveCartsInfo(c fiber.Ctx) error {
|
||||||
userID, ok := c.Locals("userID").(uint)
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
@@ -94,7 +95,7 @@ func (h *CartsHandler) RetrieveCartsInfo(c fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
|
func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
|
||||||
userID, ok := c.Locals("userID").(uint)
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
@@ -117,7 +118,7 @@ func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
|
func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
|
||||||
userID, ok := c.Locals("userID").(uint)
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"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/service/listService"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
"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/nullable"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/query_params"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/query_params"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
@@ -43,19 +44,19 @@ func (h *ListHandler) ListProducts(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
id_lang, ok := c.Locals("langID").(uint)
|
id_lang, ok := localeExtractor.GetLangID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
}
|
}
|
||||||
|
|
||||||
listing, err := h.listService.ListProducts(id_lang, paging, filters)
|
list, err := h.listService.ListProducts(id_lang, paging, filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK)))
|
return c.JSON(response.Make(&list.Items, int(list.Count), i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
var columnMappingListProducts map[string]string = map[string]string{
|
var columnMappingListProducts map[string]string = map[string]string{
|
||||||
@@ -74,19 +75,19 @@ func (h *ListHandler) ListUsers(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
id_lang, ok := c.Locals("langID").(uint)
|
id_lang, ok := localeExtractor.GetLangID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
}
|
}
|
||||||
|
|
||||||
listing, err := h.listService.ListUsers(id_lang, paging, filters)
|
list, err := h.listService.ListUsers(id_lang, paging, filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK)))
|
return c.JSON(response.Make(&list.Items, int(list.Count), i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
var columnMappingListUsers map[string]string = map[string]string{
|
var columnMappingListUsers map[string]string = map[string]string{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
|
"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/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/nullable"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
@@ -33,7 +34,7 @@ func MenuHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MenuHandler) GetCategoryTree(c fiber.Ctx) error {
|
func (h *MenuHandler) GetCategoryTree(c fiber.Ctx) error {
|
||||||
lang_id, ok := c.Locals("langID").(uint)
|
lang_id, ok := localeExtractor.GetLangID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
@@ -56,7 +57,7 @@ func (h *MenuHandler) GetCategoryTree(c fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
|
func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
|
||||||
lang_id, ok := c.Locals("langID").(uint)
|
lang_id, ok := localeExtractor.GetLangID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
@@ -86,7 +87,7 @@ func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
|
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
|
||||||
lang_id, ok := c.Locals("langID").(uint)
|
lang_id, ok := localeExtractor.GetLangID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/productTranslationService"
|
"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/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/nullable"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
@@ -41,7 +43,7 @@ func ProductTranslationHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
|
|
||||||
// GetProductDescription returns the product description for a given product ID
|
// GetProductDescription returns the product description for a given product ID
|
||||||
func (h *ProductTranslationHandler) GetProductDescription(c fiber.Ctx) error {
|
func (h *ProductTranslationHandler) GetProductDescription(c fiber.Ctx) error {
|
||||||
userID, ok := c.Locals("userID").(uint)
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
@@ -72,12 +74,18 @@ func (h *ProductTranslationHandler) GetProductDescription(c fiber.Ctx) error {
|
|||||||
|
|
||||||
// SaveProductDescription saves the description for a given product ID, in given language
|
// SaveProductDescription saves the description for a given product ID, in given language
|
||||||
func (h *ProductTranslationHandler) SaveProductDescription(c fiber.Ctx) error {
|
func (h *ProductTranslationHandler) SaveProductDescription(c fiber.Ctx) error {
|
||||||
userID, ok := c.Locals("userID").(uint)
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userRole, ok := localeExtractor.GetOriginalUserRole(c)
|
||||||
|
if !ok || userRole != model.RoleAdmin {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
|
||||||
|
}
|
||||||
|
|
||||||
productID_attribute := c.Query("productID")
|
productID_attribute := c.Query("productID")
|
||||||
productID, err := strconv.Atoi(productID_attribute)
|
productID, err := strconv.Atoi(productID_attribute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -109,12 +117,18 @@ func (h *ProductTranslationHandler) SaveProductDescription(c fiber.Ctx) error {
|
|||||||
|
|
||||||
// TranslateProductDescription returns translated product description
|
// TranslateProductDescription returns translated product description
|
||||||
func (h *ProductTranslationHandler) TranslateProductDescription(c fiber.Ctx) error {
|
func (h *ProductTranslationHandler) TranslateProductDescription(c fiber.Ctx) error {
|
||||||
userID, ok := c.Locals("userID").(uint)
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userRole, ok := localeExtractor.GetOriginalUserRole(c)
|
||||||
|
if !ok || userRole != model.RoleAdmin {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
|
||||||
|
}
|
||||||
|
|
||||||
productID_attribute := c.Query("productID")
|
productID_attribute := c.Query("productID")
|
||||||
productID, err := strconv.Atoi(productID_attribute)
|
productID, err := strconv.Atoi(productID_attribute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/meiliService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/meiliService"
|
||||||
searchservice "git.ma-al.com/goc_daniel/b2b/app/service/searchService"
|
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/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/nullable"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
@@ -36,12 +38,18 @@ func MeiliSearchHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MeiliSearchHandler) CreateIndex(c fiber.Ctx) error {
|
func (h *MeiliSearchHandler) CreateIndex(c fiber.Ctx) error {
|
||||||
id_lang, ok := c.Locals("langID").(uint)
|
id_lang, ok := localeExtractor.GetLangID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userRole, ok := localeExtractor.GetOriginalUserRole(c)
|
||||||
|
if !ok || userRole != model.RoleAdmin {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
|
||||||
|
}
|
||||||
|
|
||||||
err := h.meiliService.CreateIndex(id_lang)
|
err := h.meiliService.CreateIndex(id_lang)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("CreateIndex error: %v\n", err)
|
fmt.Printf("CreateIndex error: %v\n", err)
|
||||||
@@ -49,12 +57,11 @@ func (h *MeiliSearchHandler) CreateIndex(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
nothing := ""
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
return c.JSON(response.Make(¬hing, 0, i18n.T_(c, response.Message_OK)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
|
func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
|
||||||
id_lang, ok := c.Locals("langID").(uint)
|
id_lang, ok := localeExtractor.GetLangID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
@@ -88,7 +95,7 @@ func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MeiliSearchHandler) GetSettings(c fiber.Ctx) error {
|
func (h *MeiliSearchHandler) GetSettings(c fiber.Ctx) error {
|
||||||
id_lang, ok := c.Locals("langID").(uint)
|
id_lang, ok := localeExtractor.GetLangID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
|||||||
100
app/delivery/web/api/restricted/storage.go
Normal file
100
app/delivery/web/api/restricted/storage.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package restricted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"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/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"
|
||||||
|
"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()
|
||||||
|
|
||||||
|
// for all users
|
||||||
|
r.Get("/list-content/*", handler.ListContent)
|
||||||
|
r.Get("/download-file/*", handler.DownloadFile)
|
||||||
|
|
||||||
|
// for admins only
|
||||||
|
r.Get("/create-new-webdav-token", handler.CreateNewWebdavToken)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) CreateNewWebdavToken(c fiber.Ctx) error {
|
||||||
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
userRole, ok := localeExtractor.GetOriginalUserRole(c)
|
||||||
|
if !ok || userRole != model.RoleAdmin {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
|
||||||
|
}
|
||||||
|
|
||||||
|
new_token, err := h.storageService.NewWebdavToken(userID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&new_token, 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
198
app/delivery/web/api/webdav/storage.go
Normal file
198
app/delivery/web/api/webdav/storage.go
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
package webdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"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/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()
|
||||||
|
|
||||||
|
// for webdav use only
|
||||||
|
r.Get("/*", handler.Get)
|
||||||
|
r.Head("/*", handler.Get)
|
||||||
|
r.Put("/*", handler.Put)
|
||||||
|
r.Delete("/*", handler.Delete)
|
||||||
|
r.Add([]string{"MKCOL"}, "/*", handler.Mkcol)
|
||||||
|
r.Add([]string{"PROPFIND"}, "/*", handler.Propfind)
|
||||||
|
r.Add([]string{"PROPPATCH"}, "/*", handler.Proppatch)
|
||||||
|
r.Add([]string{"MOVE"}, "/*", handler.Move)
|
||||||
|
r.Add([]string{"COPY"}, "/*", handler.Copy)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StorageHandler) Get(c fiber.Ctx) error {
|
||||||
|
// fmt.Println("GET")
|
||||||
|
absPath, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := h.storageService.EntryInfo(absPath)
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
xml, err := h.storageService.Propfind(h.config.Storage.RootFolder, absPath, "1")
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("Content-Type", `application/xml; charset="utf-8"`)
|
||||||
|
return c.Status(http.StatusMultiStatus).SendString(xml)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
f, filename, filesize, err := h.storageService.DownloadFilePrep(absPath)
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(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) Put(c fiber.Ctx) error {
|
||||||
|
// fmt.Println("PUT")
|
||||||
|
absPath, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
var src io.Reader
|
||||||
|
if bodyStream := c.Request().BodyStream(); bodyStream != nil {
|
||||||
|
defer c.Request().CloseBodyStream()
|
||||||
|
src = bodyStream
|
||||||
|
} else {
|
||||||
|
src = bytes.NewReader(c.Body())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.storageService.Put(absPath, src)
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendStatus(http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StorageHandler) Delete(c fiber.Ctx) error {
|
||||||
|
// fmt.Println("DELETE")
|
||||||
|
absPath, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
if absPath == h.config.Storage.RootFolder {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(responseErrors.ErrAccessDenied))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.storageService.Delete(absPath)
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendStatus(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StorageHandler) Mkcol(c fiber.Ctx) error {
|
||||||
|
// fmt.Println("Mkcol")
|
||||||
|
absPath, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.storageService.Mkcol(absPath)
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendStatus(http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StorageHandler) Propfind(c fiber.Ctx) error {
|
||||||
|
// fmt.Println("PROPFIND")
|
||||||
|
absPath, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
xml, err := h.storageService.Propfind(h.config.Storage.RootFolder, absPath, "1")
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("Content-Type", `application/xml; charset="utf-8"`)
|
||||||
|
return c.Status(http.StatusMultiStatus).SendString(xml)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StorageHandler) Proppatch(c fiber.Ctx) error {
|
||||||
|
return c.SendStatus(http.StatusNotImplemented) // 501
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StorageHandler) Move(c fiber.Ctx) error {
|
||||||
|
// fmt.Println("MOVE")
|
||||||
|
srcAbsPath, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := c.Get("Destination")
|
||||||
|
if dest == "" {
|
||||||
|
return c.SendStatus(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
destAbsPath, err := h.storageService.ObtainDestPath(h.config.Storage.RootFolder, dest)
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.storageService.Move(srcAbsPath, destAbsPath)
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
return c.SendStatus(http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StorageHandler) Copy(c fiber.Ctx) error {
|
||||||
|
// fmt.Println("COPY")
|
||||||
|
srcAbsPath, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := c.Get("Destination")
|
||||||
|
if dest == "" {
|
||||||
|
return c.SendStatus(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
destAbsPath, err := h.storageService.ObtainDestPath(h.config.Storage.RootFolder, dest)
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.storageService.Copy(srcAbsPath, destAbsPath)
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(responseErrors.GetErrorStatus(err))
|
||||||
|
}
|
||||||
|
return c.SendStatus(http.StatusCreated)
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"git.ma-al.com/goc_daniel/b2b/app/delivery/web/api"
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/web/api"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/delivery/web/api/public"
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/web/api/public"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/delivery/web/api/restricted"
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/web/api/restricted"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/web/api/webdav"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/delivery/web/general"
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/web/general"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
@@ -25,6 +26,7 @@ import (
|
|||||||
type Server struct {
|
type Server struct {
|
||||||
app *fiber.App
|
app *fiber.App
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
|
webdav fiber.Router
|
||||||
api fiber.Router
|
api fiber.Router
|
||||||
public fiber.Router
|
public fiber.Router
|
||||||
restricted fiber.Router
|
restricted fiber.Router
|
||||||
@@ -42,12 +44,23 @@ func (s *Server) Cfg() *config.Config {
|
|||||||
|
|
||||||
// New creates a new server instance
|
// New creates a new server instance
|
||||||
func New() *Server {
|
func New() *Server {
|
||||||
return &Server{
|
var s Server
|
||||||
app: fiber.New(fiber.Config{
|
|
||||||
ErrorHandler: customErrorHandler,
|
app :=
|
||||||
}),
|
fiber.New(fiber.Config{
|
||||||
cfg: config.Get(),
|
ErrorHandler: customErrorHandler,
|
||||||
}
|
BodyLimit: 50 * 1024 * 1024, // 50 MB
|
||||||
|
StreamRequestBody: true,
|
||||||
|
RequestMethods: []string{
|
||||||
|
fiber.MethodGet, fiber.MethodHead, fiber.MethodPost, fiber.MethodPut,
|
||||||
|
fiber.MethodDelete, fiber.MethodConnect, fiber.MethodOptions,
|
||||||
|
fiber.MethodTrace, fiber.MethodPatch, "MKCOL", "PROPFIND", "PROPPATCH", "MOVE", "COPY",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
s.app = app
|
||||||
|
s.cfg = config.Get()
|
||||||
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup configures the server with routes and middleware
|
// Setup configures the server with routes and middleware
|
||||||
@@ -76,6 +89,8 @@ func (s *Server) Setup() error {
|
|||||||
s.public = s.api.Group("/public")
|
s.public = s.api.Group("/public")
|
||||||
s.restricted = s.api.Group("/restricted")
|
s.restricted = s.api.Group("/restricted")
|
||||||
s.restricted.Use(middleware.AuthMiddleware())
|
s.restricted.Use(middleware.AuthMiddleware())
|
||||||
|
s.webdav = s.api.Group("/webdav")
|
||||||
|
s.webdav.Use(middleware.Webdav())
|
||||||
|
|
||||||
// initialize language endpoints (general)
|
// initialize language endpoints (general)
|
||||||
api.NewLangHandler().InitLanguage(s.api, s.cfg)
|
api.NewLangHandler().InitLanguage(s.api, s.cfg)
|
||||||
@@ -115,6 +130,12 @@ func (s *Server) Setup() error {
|
|||||||
carts := s.restricted.Group("/carts")
|
carts := s.restricted.Group("/carts")
|
||||||
restricted.CartsHandlerRoutes(carts)
|
restricted.CartsHandlerRoutes(carts)
|
||||||
|
|
||||||
|
// storage (uses various authorization means)
|
||||||
|
restrictedStorage := s.restricted.Group("/storage")
|
||||||
|
webdavStorage := s.webdav.Group("/storage")
|
||||||
|
restricted.StorageHandlerRoutes(restrictedStorage)
|
||||||
|
webdav.StorageHandlerRoutes(webdavStorage)
|
||||||
|
|
||||||
s.api.All("*", func(c fiber.Ctx) error {
|
s.api.All("*", func(c fiber.Ctx) error {
|
||||||
return c.SendStatus(fiber.StatusNotFound)
|
return c.SendStatus(fiber.StatusNotFound)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ type Customer struct {
|
|||||||
EmailVerificationExpires *time.Time `json:"-"`
|
EmailVerificationExpires *time.Time `json:"-"`
|
||||||
PasswordResetToken string `gorm:"size:255" json:"-"`
|
PasswordResetToken string `gorm:"size:255" json:"-"`
|
||||||
PasswordResetExpires *time.Time `json:"-"`
|
PasswordResetExpires *time.Time `json:"-"`
|
||||||
|
WebdavToken string `gorm:"size:255" json:"-"`
|
||||||
|
WebdavExpires *time.Time `json:"-"`
|
||||||
LastPasswordResetRequest *time.Time `json:"-"`
|
LastPasswordResetRequest *time.Time `json:"-"`
|
||||||
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
||||||
LangID uint `gorm:"default:2" json:"lang_id"` // User's preferred language
|
LangID uint `gorm:"default:2" json:"lang_id"` // User's preferred language
|
||||||
@@ -82,6 +84,15 @@ type UserSession struct {
|
|||||||
IsActive bool `json:"is_active"`
|
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
|
// ToSession converts User to UserSession
|
||||||
func (u *Customer) ToSession() *UserSession {
|
func (u *Customer) ToSession() *UserSession {
|
||||||
return &UserSession{
|
return &UserSession{
|
||||||
@@ -98,6 +109,7 @@ func (u *Customer) ToSession() *UserSession {
|
|||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
Email string `json:"email" form:"email"`
|
Email string `json:"email" form:"email"`
|
||||||
Password string `json:"password" form:"password"`
|
Password string `json:"password" form:"password"`
|
||||||
|
LangID *uint `json:"lang_id" form:"lang_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRequest represents the initial registration form data
|
// RegisterRequest represents the initial registration form data
|
||||||
|
|||||||
6
app/model/entry.go
Normal file
6
app/model/entry.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type EntryInList struct {
|
||||||
|
Name string
|
||||||
|
IsFolder bool
|
||||||
|
}
|
||||||
@@ -18,9 +18,10 @@ type ProductDescription struct {
|
|||||||
AvailableLater string `gorm:"column:available_later;type:varchar(255)" json:"available_later" form:"available_later"`
|
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"`
|
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"`
|
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"`
|
||||||
|
|
||||||
ExistsInDatabse bool `gorm:"-" json:"exists_in_database"`
|
ImageLink string `gorm:"column:image_link" json:"image_link"`
|
||||||
|
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductRow struct {
|
type ProductRow struct {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (repo *ListRepo) ListProducts(id_lang uint, p find.Paging, filt *filters.Fi
|
|||||||
ps.id_product AS product_id,
|
ps.id_product AS product_id,
|
||||||
pl.name AS name,
|
pl.name AS name,
|
||||||
pl.link_rewrite AS link_rewrite,
|
pl.link_rewrite AS link_rewrite,
|
||||||
CONCAT(?, ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
|
CONCAT(?, '/', ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
|
||||||
cl.name AS category_name,
|
cl.name AS category_name,
|
||||||
p.reference AS reference,
|
p.reference AS reference,
|
||||||
COALESCE(v.variants_number, 0) AS variants_number,
|
COALESCE(v.variants_number, 0) AS variants_number,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"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/db"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
||||||
@@ -36,15 +37,36 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid
|
|||||||
IDShop: int32(constdata.SHOP_ID),
|
IDShop: int32(constdata.SHOP_ID),
|
||||||
IDLang: int32(productid_lang),
|
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
|
First(&ProductDescription).Error
|
||||||
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
// handle "not found" case only
|
// handle "not found" case only
|
||||||
ProductDescription.ExistsInDatabse = false
|
ProductDescription.ExistsInDatabase = false
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, fmt.Errorf("database error: %w", err)
|
return nil, fmt.Errorf("database error: %w", err)
|
||||||
} else {
|
} else {
|
||||||
ProductDescription.ExistsInDatabse = true
|
ProductDescription.ExistsInDatabase = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProductDescription, nil
|
return &ProductDescription, nil
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ func New() UISearchRepo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *SearchRepo) Search(index string, body []byte) (*SearchProxyResponse, error) {
|
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)
|
return r.doRequest(http.MethodPost, url, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SearchRepo) GetIndexSettings(index string) (*SearchProxyResponse, error) {
|
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)
|
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")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
if r.cfg.MailiSearch.ApiKey != "" {
|
if r.cfg.MeiliSearch.ApiKey != "" {
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", r.cfg.MailiSearch.ApiKey))
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", r.cfg.MeiliSearch.ApiKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
|
|||||||
178
app/repos/storageRepo/storageRepo.go
Normal file
178
app/repos/storageRepo/storageRepo.go
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package storageRepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UIStorageRepo interface {
|
||||||
|
SaveWebdavToken(user_id uint, hash_token string, expires_at *time.Time) error
|
||||||
|
EntryInfo(abs_path string) (os.FileInfo, error)
|
||||||
|
ListContent(abs_path string) (*[]model.EntryInList, error)
|
||||||
|
OpenFile(abs_path string) (*os.File, error)
|
||||||
|
Put(abs_path string, src io.Reader) error
|
||||||
|
Delete(abs_path string) error
|
||||||
|
Mkcol(abs_path string) error
|
||||||
|
Move(src_abs_path string, dest_abs_path string) error
|
||||||
|
Copy(src_abs_path string, dest_abs_path string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type StorageRepo struct{}
|
||||||
|
|
||||||
|
func New() UIStorageRepo {
|
||||||
|
return &StorageRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StorageRepo) SaveWebdavToken(user_id uint, hash_token string, expires_at *time.Time) error {
|
||||||
|
return db.DB.
|
||||||
|
Table("b2b_customers").
|
||||||
|
Where("id = ?", user_id).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"webdav_token": hash_token,
|
||||||
|
"webdav_expires": expires_at,
|
||||||
|
}).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
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) OpenFile(abs_path string) (*os.File, error) {
|
||||||
|
return os.Open(abs_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StorageRepo) Put(abs_path string, src io.Reader) error {
|
||||||
|
// Write to a temp file in the same directory, then atomically rename.
|
||||||
|
tmp, err := os.CreateTemp(filepath.Dir(abs_path), ".put-*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tmp_name := tmp.Name()
|
||||||
|
cleanup_tmp := true
|
||||||
|
defer func() {
|
||||||
|
_ = tmp.Close()
|
||||||
|
if cleanup_tmp {
|
||||||
|
_ = os.Remove(tmp_name)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = io.Copy(tmp, src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tmp.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tmp.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chmod(tmp_name, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Rename(tmp_name, abs_path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_tmp = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StorageRepo) Delete(abs_path string) error {
|
||||||
|
return os.RemoveAll(abs_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StorageRepo) Mkcol(abs_path string) error {
|
||||||
|
return os.Mkdir(abs_path, 0755)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
info, err := os.Stat(src_abs_path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
return r.copyDir(src_abs_path, dest_abs_path)
|
||||||
|
} else {
|
||||||
|
return r.copyFile(src_abs_path, dest_abs_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StorageRepo) copyFile(src_abs_path string, dest_abs_path string) error {
|
||||||
|
f, err := os.Open(src_abs_path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
err = r.Put(dest_abs_path, f)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StorageRepo) copyDir(src_abs_path string, dest_abs_path string) error {
|
||||||
|
if err := os.Mkdir(dest_abs_path, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(src_abs_path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
|
||||||
|
entity_src_path := filepath.Join(src_abs_path, entry.Name())
|
||||||
|
entity_dst_Path := filepath.Join(dest_abs_path, entry.Name())
|
||||||
|
|
||||||
|
if entry.IsDir() {
|
||||||
|
err = r.copyDir(entity_src_path, entity_dst_Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
err = r.copyFile(entity_src_path, entity_dst_Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -83,6 +83,15 @@ func (s *AuthService) Login(req *model.LoginRequest) (*model.AuthResponse, strin
|
|||||||
// Update last login time
|
// Update last login time
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
user.LastLoginAt = &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)
|
s.db.Save(&user)
|
||||||
|
|
||||||
// Generate access token (JWT)
|
// Generate access token (JWT)
|
||||||
@@ -443,6 +452,19 @@ func (s *AuthService) GetUserByEmail(email string) (*model.Customer, error) {
|
|||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AuthService) GetUserByWebdavToken(rawToken string) (*model.Customer, error) {
|
||||||
|
tokenHash := hashToken(rawToken)
|
||||||
|
|
||||||
|
var user model.Customer
|
||||||
|
if err := s.db.Where("webdav_token = ?", tokenHash).First(&user).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, responseErrors.ErrUserNotFound
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("database error: %w", err)
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
// createRefreshToken generates a random opaque token, stores its hash in the DB, and returns the raw token.
|
// createRefreshToken generates a random opaque token, stores its hash in the DB, and returns the raw token.
|
||||||
func (s *AuthService) createRefreshToken(userID uint) (string, error) {
|
func (s *AuthService) createRefreshToken(userID uint) (string, error) {
|
||||||
// Generate 32 random bytes → 64-char hex string
|
// Generate 32 random bytes → 64-char hex string
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/langsService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/langsService"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/templ/emails"
|
"git.ma-al.com/goc_daniel/b2b/app/templ/emails"
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/view"
|
"git.ma-al.com/goc_daniel/b2b/app/view"
|
||||||
)
|
)
|
||||||
@@ -133,6 +134,6 @@ func (s *EmailService) passwordResetEmailTemplate(name, resetURL string, langID
|
|||||||
// newUserAdminNotificationTemplate returns the HTML template for admin notification
|
// newUserAdminNotificationTemplate returns the HTML template for admin notification
|
||||||
func (s *EmailService) newUserAdminNotificationTemplate(userEmail, userName, baseURL string) string {
|
func (s *EmailService) newUserAdminNotificationTemplate(userEmail, userName, baseURL string) string {
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
emails.EmailAdminNotificationWrapper(view.EmailLayout[view.EmailAdminNotificationData]{LangID: 2, Data: view.EmailAdminNotificationData{UserEmail: userEmail, UserName: userName, BaseURL: baseURL}}).Render(context.Background(), &buf)
|
emails.EmailAdminNotificationWrapper(view.EmailLayout[view.EmailAdminNotificationData]{LangID: constdata.ADMIN_NOTIFICATION_LANGUAGE, Data: view.EmailAdminNotificationData{UserEmail: userEmail, UserName: userName, BaseURL: baseURL}}).Render(context.Background(), &buf)
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ type MeiliService struct {
|
|||||||
func New() *MeiliService {
|
func New() *MeiliService {
|
||||||
|
|
||||||
client := meilisearch.New(
|
client := meilisearch.New(
|
||||||
config.Get().MailiSearch.ServerURL,
|
config.Get().MeiliSearch.ServerURL,
|
||||||
meilisearch.WithAPIKey(config.Get().MailiSearch.ApiKey),
|
meilisearch.WithAPIKey(config.Get().MeiliSearch.ApiKey),
|
||||||
)
|
)
|
||||||
|
|
||||||
return &MeiliService{
|
return &MeiliService{
|
||||||
|
|||||||
283
app/service/storageService/storageService.go
Normal file
283
app/service/storageService/storageService.go
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
package storageService
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/repos/storageRepo"
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StorageService struct {
|
||||||
|
storageRepo storageRepo.UIStorageRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *StorageService {
|
||||||
|
return &StorageService{
|
||||||
|
storageRepo: storageRepo.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageService) EntryInfo(abs_path string) (os.FileInfo, error) {
|
||||||
|
return s.storageRepo.EntryInfo(abs_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageService) NewWebdavToken(user_id uint) (string, error) {
|
||||||
|
b := make([]byte, constdata.NBYTES_IN_WEBDAV_TOKEN)
|
||||||
|
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw_token := hex.EncodeToString(b)
|
||||||
|
hash_token_bytes := sha256.Sum256([]byte(raw_token))
|
||||||
|
hash_token := hex.EncodeToString(hash_token_bytes[:])
|
||||||
|
expires_at := time.Now().Add(24 * time.Hour)
|
||||||
|
|
||||||
|
return raw_token, s.storageRepo.SaveWebdavToken(user_id, hash_token, &expires_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) 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) Propfind(root string, abs_path string, depth string) (string, error) {
|
||||||
|
href := href(root, abs_path)
|
||||||
|
|
||||||
|
max_depth := 0
|
||||||
|
switch depth {
|
||||||
|
case "0":
|
||||||
|
max_depth = 0
|
||||||
|
case "1":
|
||||||
|
max_depth = 1
|
||||||
|
case "infinity":
|
||||||
|
max_depth = 32
|
||||||
|
default:
|
||||||
|
max_depth = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := s.storageRepo.EntryInfo(abs_path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
xml := `<?xml version="1.0" encoding="utf-8"?>` +
|
||||||
|
`<D:multistatus xmlns:D="DAV:">`
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
href = ensureTrailingSlash(href)
|
||||||
|
next_xml, err := buildDirPropResponse(abs_path, href, info, max_depth)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
xml += next_xml
|
||||||
|
} else {
|
||||||
|
xml += buildFilePropResponse(href, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
xml += `</D:multistatus>`
|
||||||
|
|
||||||
|
return xml, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageService) Put(abs_path string, src io.Reader) error {
|
||||||
|
return s.storageRepo.Put(abs_path, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageService) Delete(abs_path string) error {
|
||||||
|
return s.storageRepo.Delete(abs_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageService) Mkcol(abs_path string) error {
|
||||||
|
_, err := s.storageRepo.EntryInfo(abs_path)
|
||||||
|
if err == nil {
|
||||||
|
return responseErrors.ErrNameTaken
|
||||||
|
} else if os.IsNotExist(err) {
|
||||||
|
return s.storageRepo.Mkcol(abs_path)
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageService) Move(src_abs_path string, dest_abs_path string) error {
|
||||||
|
return s.storageRepo.Move(src_abs_path, dest_abs_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageService) Copy(src_abs_path string, dest_abs_path string) error {
|
||||||
|
return s.storageRepo.Copy(src_abs_path, dest_abs_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFilePropResponse(href string, info os.FileInfo) string {
|
||||||
|
name := info.Name()
|
||||||
|
return "" +
|
||||||
|
"<D:response>" +
|
||||||
|
"<D:href>" + xmlEscape(href) + "</D:href>" +
|
||||||
|
"<D:propstat>" +
|
||||||
|
"<D:prop>" +
|
||||||
|
"<D:displayname>" + xmlEscape(name) + "</D:displayname>" +
|
||||||
|
"<D:getcontentlength>" + strconv.FormatInt(info.Size(), 10) + "</D:getcontentlength>" +
|
||||||
|
"<D:getlastmodified>" + xmlEscape(info.ModTime().UTC().Format(http.TimeFormat)) + "</D:getlastmodified>" +
|
||||||
|
"<D:resourcetype/>" +
|
||||||
|
"</D:prop>" +
|
||||||
|
"<D:status>HTTP/1.1 200 OK</D:status>" +
|
||||||
|
"</D:propstat>" +
|
||||||
|
"</D:response>"
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDirPropResponse(abs_path string, href string, info os.FileInfo, max_depth int) (string, error) {
|
||||||
|
name := info.Name()
|
||||||
|
|
||||||
|
xml := "" +
|
||||||
|
"<D:response>" +
|
||||||
|
"<D:href>" + xmlEscape(ensureTrailingSlash(href)) + "</D:href>" +
|
||||||
|
"<D:propstat>" +
|
||||||
|
"<D:prop>" +
|
||||||
|
"<D:displayname>" + xmlEscape(name) + "</D:displayname>" +
|
||||||
|
"<D:resourcetype><D:collection/></D:resourcetype>" +
|
||||||
|
"<D:getlastmodified>" + xmlEscape(info.ModTime().UTC().Format(http.TimeFormat)) + "</D:getlastmodified>" +
|
||||||
|
"</D:prop>" +
|
||||||
|
"<D:status>HTTP/1.1 200 OK</D:status>" +
|
||||||
|
"</D:propstat>" +
|
||||||
|
"</D:response>"
|
||||||
|
|
||||||
|
if max_depth <= 0 {
|
||||||
|
return xml, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(abs_path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
child_abs_path := filepath.Join(abs_path, entry.Name())
|
||||||
|
child_href := path.Join(href, entry.Name())
|
||||||
|
|
||||||
|
child_info, err := entry.Info()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var xml_next string
|
||||||
|
if entry.IsDir() {
|
||||||
|
xml_next, err = buildDirPropResponse(child_abs_path, ensureTrailingSlash(child_href), child_info, max_depth-1)
|
||||||
|
} else {
|
||||||
|
xml_next = buildFilePropResponse(child_href, child_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
xml += xml_next
|
||||||
|
}
|
||||||
|
|
||||||
|
return xml, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureTrailingSlash(s string) string {
|
||||||
|
if s == "/" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(s, "/") {
|
||||||
|
return s + "/"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func xmlEscape(s string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
xml.EscapeText(&b, []byte(s))
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns href based on file's absolute path. Doesn't validate abs_path
|
||||||
|
func href(root string, abs_path string) string {
|
||||||
|
rel, _ := filepath.Rel(root, abs_path)
|
||||||
|
|
||||||
|
if rel == "." {
|
||||||
|
return constdata.WEBDAV_HREF_ROOT + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
rel = filepath.ToSlash(rel)
|
||||||
|
|
||||||
|
parts := strings.Split(rel, "/")
|
||||||
|
for i, p := range parts {
|
||||||
|
parts[i] = url.PathEscape(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimRight(constdata.WEBDAV_HREF_ROOT, "/") + "/" + strings.Join(parts, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AbsPath extracts an absolute path and validates it
|
||||||
|
func (s *StorageService) AbsPath(root string, relative_path string) (string, error) {
|
||||||
|
decoded, err := url.PathUnescape(relative_path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_name := filepath.Clean(decoded)
|
||||||
|
full_path := filepath.Join(root, clean_name)
|
||||||
|
|
||||||
|
if full_path != root && !strings.HasPrefix(full_path, root+"/") {
|
||||||
|
return "", responseErrors.ErrAccessDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
return full_path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObtainDestPath extracts the absolute path based on URL absolute path
|
||||||
|
func (s *StorageService) ObtainDestPath(root string, dest_path string) (string, error) {
|
||||||
|
idx := strings.Index(dest_path, constdata.WEBDAV_TRIMMED_ROOT)
|
||||||
|
if idx == -1 {
|
||||||
|
return "", responseErrors.ErrAccessDenied
|
||||||
|
}
|
||||||
|
prefix_removed := dest_path[idx+len(constdata.WEBDAV_TRIMMED_ROOT):]
|
||||||
|
|
||||||
|
decoded, err := url.PathUnescape(prefix_removed)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_dest_path := filepath.Clean(decoded)
|
||||||
|
if clean_dest_path == "" {
|
||||||
|
return root, nil
|
||||||
|
} else if strings.HasPrefix(clean_dest_path, "/") {
|
||||||
|
return root + "/" + strings.TrimPrefix(clean_dest_path, "/"), nil
|
||||||
|
} else {
|
||||||
|
return "", responseErrors.ErrAccessDenied
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ package constdata
|
|||||||
const PASSWORD_VALIDATION_REGEX = `^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$`
|
const PASSWORD_VALIDATION_REGEX = `^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$`
|
||||||
const SHOP_ID = 1
|
const SHOP_ID = 1
|
||||||
const SHOP_DEFAULT_LANGUAGE = 1
|
const SHOP_DEFAULT_LANGUAGE = 1
|
||||||
|
const ADMIN_NOTIFICATION_LANGUAGE = 2
|
||||||
|
|
||||||
// CATEGORY_TREE_ROOT_ID corresponds to id_category in ps_category which has is_root_category=1
|
// CATEGORY_TREE_ROOT_ID corresponds to id_category in ps_category which has is_root_category=1
|
||||||
const CATEGORY_TREE_ROOT_ID = 2
|
const CATEGORY_TREE_ROOT_ID = 2
|
||||||
@@ -11,5 +12,9 @@ const CATEGORY_TREE_ROOT_ID = 2
|
|||||||
const MAX_AMOUNT_OF_CARTS_PER_USER = 10
|
const MAX_AMOUNT_OF_CARTS_PER_USER = 10
|
||||||
const DEFAULT_NEW_CART_NAME = "new cart"
|
const DEFAULT_NEW_CART_NAME = "new cart"
|
||||||
|
|
||||||
const USER_LOCALES_NAME = "user"
|
const USER_LOCALE = "user"
|
||||||
const USER_LOCALES_ID = "userID"
|
|
||||||
|
// WEBDAV
|
||||||
|
const NBYTES_IN_WEBDAV_TOKEN = 32
|
||||||
|
const WEBDAV_HREF_ROOT = "http://localhost:3000/api/v1/webdav/storage"
|
||||||
|
const WEBDAV_TRIMMED_ROOT = "localhost:3000/api/v1/webdav/storage"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -177,7 +178,7 @@ func (s *TranslationsStore) ReloadTranslations(translations []model.Translation)
|
|||||||
|
|
||||||
// T_ is meant to be used to translate error messages and other system communicates.
|
// T_ is meant to be used to translate error messages and other system communicates.
|
||||||
func T_[T ~string](c fiber.Ctx, key T, params ...interface{}) string {
|
func T_[T ~string](c fiber.Ctx, key T, params ...interface{}) string {
|
||||||
if langID, ok := c.Locals("langID").(uint); ok {
|
if langID, ok := localeExtractor.GetLangID(c); ok {
|
||||||
parts := strings.Split(string(key), ".")
|
parts := strings.Split(string(key), ".")
|
||||||
|
|
||||||
if len(parts) >= 2 {
|
if len(parts) >= 2 {
|
||||||
|
|||||||
31
app/utils/localeExtractor/localeExtractor.go
Normal file
31
app/utils/localeExtractor/localeExtractor.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOriginalUserRole(c fiber.Ctx) (model.CustomerRole, bool) {
|
||||||
|
user_locale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
|
||||||
|
if !ok || user_locale.OriginalUser == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return user_locale.OriginalUser.Role, true
|
||||||
|
}
|
||||||
@@ -9,13 +9,14 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// Typed errors for request validation and authentication
|
// Typed errors for request validation and authentication
|
||||||
ErrInvalidBody = errors.New("invalid request body")
|
ErrInvalidBody = errors.New("invalid request body")
|
||||||
ErrNotAuthenticated = errors.New("not authenticated")
|
ErrNotAuthenticated = errors.New("not authenticated")
|
||||||
ErrUserNotFound = errors.New("user not found")
|
ErrUserNotFound = errors.New("user not found")
|
||||||
ErrUserInactive = errors.New("user account is inactive")
|
ErrUserInactive = errors.New("user account is inactive")
|
||||||
ErrInvalidToken = errors.New("invalid token")
|
ErrInvalidToken = errors.New("invalid token")
|
||||||
ErrTokenExpired = errors.New("token has expired")
|
ErrTokenExpired = errors.New("token has expired")
|
||||||
ErrTokenRequired = errors.New("token is required")
|
ErrTokenRequired = errors.New("token is required")
|
||||||
|
ErrAdminAccessRequired = errors.New("admin access is required")
|
||||||
|
|
||||||
// Typed errors for logging in and registering
|
// Typed errors for logging in and registering
|
||||||
ErrInvalidCredentials = errors.New("invalid email or password")
|
ErrInvalidCredentials = errors.New("invalid email or password")
|
||||||
@@ -59,6 +60,13 @@ var (
|
|||||||
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
|
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
|
||||||
ErrUserHasNoSuchCart = errors.New("user does not have cart with given id")
|
ErrUserHasNoSuchCart = errors.New("user does not have cart with given id")
|
||||||
ErrProductOrItsVariationDoesNotExist = errors.New("product or its variation with given ids does not exist")
|
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
|
// Error represents an error with HTTP status code
|
||||||
@@ -111,6 +119,8 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
return i18n.T_(c, "error.err_token_required")
|
return i18n.T_(c, "error.err_token_required")
|
||||||
case errors.Is(err, ErrRefreshTokenRequired):
|
case errors.Is(err, ErrRefreshTokenRequired):
|
||||||
return i18n.T_(c, "error.err_refresh_token_required")
|
return i18n.T_(c, "error.err_refresh_token_required")
|
||||||
|
case errors.Is(err, ErrAdminAccessRequired):
|
||||||
|
return i18n.T_(c, "error.err_admin_access_required")
|
||||||
case errors.Is(err, ErrBadLangID):
|
case errors.Is(err, ErrBadLangID):
|
||||||
return i18n.T_(c, "error.err_bad_lang_id")
|
return i18n.T_(c, "error.err_bad_lang_id")
|
||||||
case errors.Is(err, ErrBadCountryID):
|
case errors.Is(err, ErrBadCountryID):
|
||||||
@@ -162,6 +172,17 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
case errors.Is(err, ErrProductOrItsVariationDoesNotExist):
|
case errors.Is(err, ErrProductOrItsVariationDoesNotExist):
|
||||||
return i18n.T_(c, "error.product_or_its_variation_does_not_exist")
|
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:
|
default:
|
||||||
return i18n.T_(c, "error.err_internal_server_error")
|
return i18n.T_(c, "error.err_internal_server_error")
|
||||||
}
|
}
|
||||||
@@ -184,6 +205,7 @@ func GetErrorStatus(err error) int {
|
|||||||
errors.Is(err, ErrEmailPasswordRequired),
|
errors.Is(err, ErrEmailPasswordRequired),
|
||||||
errors.Is(err, ErrTokenRequired),
|
errors.Is(err, ErrTokenRequired),
|
||||||
errors.Is(err, ErrRefreshTokenRequired),
|
errors.Is(err, ErrRefreshTokenRequired),
|
||||||
|
errors.Is(err, ErrAdminAccessRequired),
|
||||||
errors.Is(err, ErrBadLangID),
|
errors.Is(err, ErrBadLangID),
|
||||||
errors.Is(err, ErrBadCountryID),
|
errors.Is(err, ErrBadCountryID),
|
||||||
errors.Is(err, ErrPasswordsDoNotMatch),
|
errors.Is(err, ErrPasswordsDoNotMatch),
|
||||||
@@ -203,7 +225,12 @@ func GetErrorStatus(err error) int {
|
|||||||
errors.Is(err, ErrRootNeverReached),
|
errors.Is(err, ErrRootNeverReached),
|
||||||
errors.Is(err, ErrMaxAmtOfCartsReached),
|
errors.Is(err, ErrMaxAmtOfCartsReached),
|
||||||
errors.Is(err, ErrUserHasNoSuchCart),
|
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
|
return fiber.StatusBadRequest
|
||||||
case errors.Is(err, ErrEmailExists):
|
case errors.Is(err, ErrEmailExists):
|
||||||
return fiber.StatusConflict
|
return fiber.StatusConflict
|
||||||
|
|||||||
@@ -1,39 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<suspense>
|
<suspense>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20">
|
<div class="flex gap-10">
|
||||||
<!-- <UNavigationMenu orientation="vertical" :items="listing" class="data-[orientation=vertical]:w-48">
|
<CategoryMenu />
|
||||||
<template #item="{ item, active }">
|
<div class="w-full flex flex-col items-center gap-4">
|
||||||
<div class="flex items-center gap-2 px-3 py-2">
|
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" />
|
||||||
<UIcon name="i-heroicons-book-open" />
|
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
|
||||||
<span>{{ item.name }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</UNavigationMenu> -->
|
|
||||||
<h1 class="text-2xl font-bold mb-6 text-gray-900 dark:text-white">Products</h1>
|
|
||||||
<div v-if="loading" class="text-center py-8">
|
|
||||||
<span class="text-gray-600 dark:text-gray-400">Loading products...</span>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="error" class="mb-4 p-3 bg-red-100 text-red-700 rounded">
|
|
||||||
{{ error }}
|
|
||||||
</div>
|
|
||||||
<div v-else class="overflow-x-auto">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<CategoryMenuListing />
|
|
||||||
<UTable :data="productsList" :columns="columns" class="flex-1">
|
|
||||||
<template #expanded="{ row }">
|
|
||||||
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
|
|
||||||
thead: 'hidden'
|
|
||||||
}" />
|
|
||||||
</template>
|
|
||||||
</UTable>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center items-center py-8">
|
|
||||||
<UPagination v-model:page="page" :total="total" :page-size="perPage" />
|
|
||||||
</div>
|
|
||||||
<div v-if="productsList.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
|
||||||
No products found
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</component>
|
||||||
@@ -46,26 +18,20 @@ import { useFetchJson } from '@/composable/useFetchJson'
|
|||||||
import Default from '@/layouts/default.vue'
|
import Default from '@/layouts/default.vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import type { TableColumn } from '@nuxt/ui'
|
import type { TableColumn } from '@nuxt/ui'
|
||||||
import CategoryMenuListing from '../inner/categoryMenuListing.vue'
|
import CategoryMenu from '../inner/categoryMenu.vue'
|
||||||
|
import type { Product } from '@/types/product'
|
||||||
interface Product {
|
|
||||||
reference: number
|
|
||||||
product_id: number
|
|
||||||
name: string
|
|
||||||
image_link: string
|
|
||||||
link_rewrite: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
const perPage = ref(15)
|
||||||
const page = computed({
|
const page = computed({
|
||||||
get: () => Number(route.query.page) || 1,
|
get: () => Number(route.query.p) || 1,
|
||||||
set: (val: number) => {
|
set: (val: number) => {
|
||||||
router.push({
|
router.push({
|
||||||
query: {
|
query: {
|
||||||
...route.query,
|
...route.query,
|
||||||
page: val
|
p: val
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -101,7 +67,6 @@ const sortField = computed({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const perPage = ref(15)
|
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
|
|
||||||
interface ApiResponse {
|
interface ApiResponse {
|
||||||
@@ -162,17 +127,25 @@ async function fetchProductList() {
|
|||||||
error.value = null
|
error.value = null
|
||||||
|
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
|
|
||||||
Object.entries(route.query).forEach(([key, value]) => {
|
Object.entries(route.query).forEach(([key, value]) => {
|
||||||
if (value) params.append(key, String(value))
|
if (value === undefined || value === null) return
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach(v => params.append(key, String(v)))
|
||||||
|
} else {
|
||||||
|
params.append(key, String(value))
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const url = `/api/v1/restricted/list-products/get-listing?${params}`
|
if (route.params.category_id)
|
||||||
|
params.append('category_id', String(route.params.category_id))
|
||||||
|
|
||||||
|
const url = `/api/v1/restricted/list/list-products?elems=${perPage.value}&${params.toString()}`
|
||||||
try {
|
try {
|
||||||
const response = await useFetchJson<ApiResponse>(url)
|
const response = await useFetchJson<ApiResponse>(url)
|
||||||
productsList.value = response.items || []
|
productsList.value = response.items || []
|
||||||
total.value = response.count || 0
|
total.value = Number(response.count) || 0
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
error.value = e instanceof Error ? e.message : 'Failed to load products'
|
error.value = e instanceof Error ? e.message : 'Failed to load products'
|
||||||
} finally {
|
} finally {
|
||||||
@@ -182,16 +155,11 @@ async function fetchProductList() {
|
|||||||
|
|
||||||
function goToProduct(productId: number) {
|
function goToProduct(productId: number) {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'product-detail',
|
name: 'customer-product-details',
|
||||||
params: { id: productId }
|
params: { product_id: productId }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedCount = ref({
|
|
||||||
product_id: null,
|
|
||||||
count: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
function getIcon(name: string) {
|
function getIcon(name: string) {
|
||||||
if (sortField.value[0] === name) {
|
if (sortField.value[0] === name) {
|
||||||
if (sortField.value[1] === 'asc') return 'i-lucide-arrow-up-narrow-wide'
|
if (sortField.value[1] === 'asc') return 'i-lucide-arrow-up-narrow-wide'
|
||||||
@@ -205,25 +173,7 @@ const UInput = resolveComponent('UInput')
|
|||||||
const UButton = resolveComponent('UButton')
|
const UButton = resolveComponent('UButton')
|
||||||
const UIcon = resolveComponent('UIcon')
|
const UIcon = resolveComponent('UIcon')
|
||||||
|
|
||||||
const columns: TableColumn<Payment>[] = [
|
const columns: TableColumn<Product>[] = [
|
||||||
{
|
|
||||||
id: 'expand',
|
|
||||||
cell: ({ row }) =>
|
|
||||||
h(UButton, {
|
|
||||||
color: 'neutral',
|
|
||||||
variant: 'ghost',
|
|
||||||
icon: 'i-lucide-chevron-down',
|
|
||||||
square: true,
|
|
||||||
'aria-label': 'Expand',
|
|
||||||
ui: {
|
|
||||||
leadingIcon: [
|
|
||||||
'transition-transform',
|
|
||||||
row.getIsExpanded() ? 'duration-200 rotate-180' : ''
|
|
||||||
]
|
|
||||||
},
|
|
||||||
onClick: () => row.toggleExpanded()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
accessorKey: 'product_id',
|
accessorKey: 'product_id',
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
@@ -314,108 +264,17 @@ const columns: TableColumn<Payment>[] = [
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => row.getValue('quantity') as number
|
cell: ({ row }) => row.getValue('quantity') as number
|
||||||
},
|
},
|
||||||
{
|
|
||||||
accessorKey: 'count',
|
|
||||||
header: 'Count',
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return h(UInputNumber, {
|
|
||||||
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
|
|
||||||
'onUpdate:modelValue': (val: number) => {
|
|
||||||
if (val)
|
|
||||||
selectedCount.value = {
|
|
||||||
product_id: row.original.product_id,
|
|
||||||
count: val
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
selectedCount.value = {
|
|
||||||
product_id: null,
|
|
||||||
count: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
min: 0,
|
|
||||||
max: row.original.quantity
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
accessorKey: 'count',
|
accessorKey: 'count',
|
||||||
header: '',
|
header: '',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return h(UButton, {
|
return h(UButton, {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
console.log('Clicked', row.original)
|
goToProduct(row.original.product_id)
|
||||||
},
|
},
|
||||||
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
color: 'primary',
|
||||||
disabled: selectedCount.value.product_id !== row.original.product_id,
|
|
||||||
variant: 'solid'
|
variant: 'solid'
|
||||||
}, 'Add to cart')
|
}, () => 'Show product')
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const columnsChild: TableColumn<Payment>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: 'product_id',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => `#${row.getValue('product_id') as number}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'image_link',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return h('img', {
|
|
||||||
src: row.getValue('image_link') as string,
|
|
||||||
style: 'width:40px;height:40px;object-fit:cover;'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'name',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => row.getValue('name') as string
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'quantity',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => row.getValue('quantity') as number
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'count',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return h(UInputNumber, {
|
|
||||||
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
|
|
||||||
'onUpdate:modelValue': (val: number) => {
|
|
||||||
if (val)
|
|
||||||
selectedCount.value = {
|
|
||||||
product_id: row.original.product_id,
|
|
||||||
count: val
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
selectedCount.value = {
|
|
||||||
product_id: null,
|
|
||||||
count: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
min: 0,
|
|
||||||
max: row.original.quantity
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'count',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return h(UButton, {
|
|
||||||
onClick: () => {
|
|
||||||
console.log('Clicked', row.original)
|
|
||||||
},
|
|
||||||
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
|
||||||
disabled: selectedCount.value.product_id !== row.original.product_id,
|
|
||||||
variant: 'solid'
|
|
||||||
}, 'Add to cart')
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -427,4 +286,4 @@ watch(
|
|||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,126 +1,130 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container my-10 mx-auto ">
|
<div class="container my-10 mx-auto ">
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex items-end justify-between gap-4 mb-6 bg-(--second-light) dark:bg-(--main-dark) border border-(--border-light) dark:border-(--border-dark) p-4 rounded-md">
|
class="flex items-end justify-between gap-4 mb-6 bg-(--second-light) dark:bg-(--main-dark) border border-(--border-light) dark:border-(--border-dark) p-4 rounded-md">
|
||||||
<div class="flex items-end gap-3">
|
<div class="flex items-end gap-3">
|
||||||
<USelect v-model="selectedLanguage" :items="availableLangs" variant="outline" class="w-40!" valueKey="iso_code">
|
<USelect v-model="selectedLanguage" :items="availableLangs" variant="outline" class="w-40!"
|
||||||
<template #default="{ modelValue }">
|
valueKey="iso_code">
|
||||||
<div class="flex items-center gap-2">
|
<template #default="{ modelValue }">
|
||||||
<span class="text-md">{{availableLangs.find(x => x.iso_code == modelValue)?.flag}}</span>
|
<div class="flex items-center gap-2">
|
||||||
<span class="font-medium dark:text-white text-black">{{availableLangs.find(x => x.iso_code ==
|
<span class="text-md">{{availableLangs.find(x => x.iso_code == modelValue)?.flag}}</span>
|
||||||
modelValue)?.name}}</span>
|
<span class="font-medium dark:text-white text-black">{{availableLangs.find(x => x.iso_code ==
|
||||||
</div>
|
modelValue)?.name}}</span>
|
||||||
</template>
|
</div>
|
||||||
<template #item-leading="{ item }">
|
</template>
|
||||||
<div class="flex items-center rounded-md cursor-pointer transition-colors">
|
<template #item-leading="{ item }">
|
||||||
<span class="text-md">{{ item.flag }}</span>
|
<div class="flex items-center rounded-md cursor-pointer transition-colors">
|
||||||
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
<span class="text-md">{{ item.flag }}</span>
|
||||||
</div>
|
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
||||||
</template>
|
</div>
|
||||||
</USelect>
|
</template>
|
||||||
|
</USelect>
|
||||||
|
</div>
|
||||||
|
<UButton @click="translateToSelectedLanguage" color="primary" :loading="translating"
|
||||||
|
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!">
|
||||||
|
Translate
|
||||||
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
<UButton @click="translateToSelectedLanguage" color="primary" :loading="translating"
|
|
||||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!">
|
|
||||||
Translate
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="translating" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
<div v-if="translating" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||||
<div class="flex flex-col items-center gap-4 p-8 bg-(--main-light) dark:bg-(--main-dark) rounded-lg shadow-xl">
|
<div class="flex flex-col items-center gap-4 p-8 bg-(--main-light) dark:bg-(--main-dark) rounded-lg shadow-xl">
|
||||||
|
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
||||||
|
<p class="text-lg font-medium dark:text-white text-black">Translating...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="productStore.loading" class="flex items-center justify-center py-20">
|
||||||
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
||||||
<p class="text-lg font-medium dark:text-white text-black">Translating...</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-else-if="productStore.error" class="flex items-center justify-center py-20">
|
||||||
|
<p class="text-red-500">{{ productStore.error }}</p>
|
||||||
<div v-if="productStore.loading" class="flex items-center justify-center py-20">
|
|
||||||
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
|
||||||
</div>
|
|
||||||
<div v-else-if="productStore.error" class="flex items-center justify-center py-20">
|
|
||||||
<p class="text-red-500">{{ productStore.error }}</p>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="productStore.productDescription" class="flex items-start gap-30">
|
|
||||||
<div class="w-80 h-80 bg-(--second-light) dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
|
||||||
<span class="text-gray-500 dark:text-gray-400">Product Image</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div v-else-if="productStore.productDescription" class="flex items-start gap-30">
|
||||||
<p class="text-[25px] font-bold text-black dark:text-white">
|
<div class="w-80 h-80 bg-(--second-light) dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
||||||
{{ productStore.productDescription.name || 'Product Name' }}
|
<span class="text-gray-500 dark:text-gray-400">Product Image</span>
|
||||||
</p>
|
</div>
|
||||||
<p v-html="productStore.productDescription.description_short" class="text-black dark:text-white"></p>
|
<div class="flex flex-col gap-2">
|
||||||
<div class="space-y-[10px]">
|
<p class="text-[25px] font-bold text-black dark:text-white">
|
||||||
<div class="flex items-center gap-1">
|
{{ productStore.productDescription.name || 'Product Name' }}
|
||||||
<UIcon name="lets-icons:done-ring-round-fill" class="text-[20px] text-green-600" />
|
</p>
|
||||||
<p class="text-[16px] font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
<p v-html="productStore.productDescription.description_short" class="text-black dark:text-white"></p>
|
||||||
{{ productStore.productDescription.available_now }}
|
<div class="space-y-[10px]">
|
||||||
</p>
|
<div class="flex items-center gap-1">
|
||||||
|
<UIcon name="lets-icons:done-ring-round-fill" class="text-[20px] text-green-600" />
|
||||||
|
<p class="text-[16px] font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
||||||
|
{{ productStore.productDescription.available_now }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<UIcon name="marketeq:car-shipping" class="text-[25px] text-green-600" />
|
||||||
|
<p class="text-[18px] font-bold text-black dark:text-white">
|
||||||
|
{{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
</div>
|
||||||
<UIcon name="marketeq:car-shipping" class="text-[25px] text-green-600" />
|
</div>
|
||||||
<p class="text-[18px] font-bold text-black dark:text-white">
|
|
||||||
{{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}
|
<div v-if="productStore.productDescription" class="mt-16">
|
||||||
</p>
|
<div class="flex gap-4 my-6">
|
||||||
|
<UButton @click="activeTab = 'description'"
|
||||||
|
:class="['cursor-pointer', activeTab === 'description' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
||||||
|
variant="outline">
|
||||||
|
<p class="dark:text-white">Description</p>
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<UButton @click="activeTab = 'usage'"
|
||||||
|
:class="['cursor-pointer', activeTab === 'usage' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
||||||
|
variant="outline">
|
||||||
|
<p class="dark:text-white">Usage</p>
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="activeTab === 'usage'"
|
||||||
|
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
||||||
|
|
||||||
|
<div class="flex justify-end items-center gap-3 mb-4">
|
||||||
|
<UButton v-if="!isEditing" @click="enableEdit"
|
||||||
|
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
||||||
|
<p class="text-white">Change Text</p>
|
||||||
|
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
||||||
|
</UButton>
|
||||||
|
<UButton v-if="isEditing" @click="saveText" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
||||||
|
<p class="dark:text-white text-black">Save the edited text</p>
|
||||||
|
</UButton>
|
||||||
|
<UButton v-if="isEditing" @click="cancelEdit" color="neutral" variant="outline"
|
||||||
|
class="p-2.5 cursor-pointer">
|
||||||
|
Cancel
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
<p ref="usageRef" v-html="productStore.productDescription.usage"
|
||||||
|
class="flex flex-col justify-center w-full text-start dark:text-white! text-black!"></p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="activeTab === 'description'"
|
||||||
|
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
||||||
|
<div class="flex items-center justify-end gap-3 mb-4">
|
||||||
|
<UButton v-if="!descriptionEdit.isEditing.value" @click="enableDescriptionEdit"
|
||||||
|
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
||||||
|
<p class="text-white">Change Text</p>
|
||||||
|
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
||||||
|
</UButton>
|
||||||
|
<UButton v-if="descriptionEdit.isEditing.value" @click="saveDescription" color="neutral" variant="outline"
|
||||||
|
class="p-2.5 cursor-pointer">
|
||||||
|
<p class="dark:text-white text-black ">Save the edited text</p>
|
||||||
|
</UButton>
|
||||||
|
<UButton v-if="descriptionEdit.isEditing.value" @click="cancelDescriptionEdit" color="neutral"
|
||||||
|
variant="outline" class="p-2.5 cursor-pointer">Cancel</UButton>
|
||||||
|
</div>
|
||||||
|
<div ref="descriptionRef" v-html="productStore.productDescription.description"
|
||||||
|
class="flex flex-col justify-center dark:text-white text-black">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="productStore.productDescription" class="mt-16">
|
|
||||||
<div class="flex gap-4 my-6">
|
|
||||||
<UButton @click="activeTab = 'description'"
|
|
||||||
:class="['cursor-pointer', activeTab === 'description' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
|
||||||
variant="outline">
|
|
||||||
<p class="dark:text-white">Description</p>
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
<UButton @click="activeTab = 'usage'"
|
|
||||||
:class="['cursor-pointer', activeTab === 'usage' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
|
||||||
variant="outline">
|
|
||||||
<p class="dark:text-white">Usage</p>
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="activeTab === 'usage'"
|
|
||||||
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
|
||||||
|
|
||||||
<div class="flex justify-end items-center gap-3 mb-4">
|
|
||||||
<UButton v-if="!isEditing" @click="enableEdit"
|
|
||||||
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
|
||||||
<p class="text-white">Change Text</p>
|
|
||||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
|
||||||
</UButton>
|
|
||||||
<UButton v-if="isEditing" @click="saveText" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
|
||||||
<p class="dark:text-white text-black">Save the edited text</p>
|
|
||||||
</UButton>
|
|
||||||
<UButton v-if="isEditing" @click="cancelEdit" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
|
||||||
Cancel
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
<p ref="usageRef" v-html="productStore.productDescription.usage"
|
|
||||||
class="flex flex-col justify-center w-full text-start dark:text-white! text-black!"></p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="activeTab === 'description'"
|
|
||||||
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
|
||||||
<div class="flex items-center justify-end gap-3 mb-4">
|
|
||||||
<UButton v-if="!descriptionEdit.isEditing.value" @click="enableDescriptionEdit"
|
|
||||||
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
|
||||||
<p class="text-white">Change Text</p>
|
|
||||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
|
||||||
</UButton>
|
|
||||||
<UButton v-if="descriptionEdit.isEditing.value" @click="saveDescription" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
|
||||||
<p class="dark:text-white text-black ">Save the edited text</p>
|
|
||||||
</UButton>
|
|
||||||
<UButton v-if="descriptionEdit.isEditing.value" @click="cancelDescriptionEdit" color="neutral" variant="outline" class="p-2.5 cursor-pointer">Cancel</UButton>
|
|
||||||
</div>
|
|
||||||
<div ref="descriptionRef" v-html="productStore.productDescription.description"
|
|
||||||
class="flex flex-col justify-center dark:text-white text-black">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -142,7 +146,7 @@ const isEditing = ref(false)
|
|||||||
|
|
||||||
const availableLangs = computed(() => langs)
|
const availableLangs = computed(() => langs)
|
||||||
|
|
||||||
const selectedLanguage = ref('pl')
|
const selectedLanguage = ref('en')
|
||||||
|
|
||||||
const currentLangId = ref(2)
|
const currentLangId = ref(2)
|
||||||
const productID = ref<number>(0)
|
const productID = ref<number>(0)
|
||||||
@@ -176,7 +180,7 @@ const translateToSelectedLanguage = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const id = route.params.id
|
const id = route.params.product_id
|
||||||
if (id) {
|
if (id) {
|
||||||
productID.value = Number(id)
|
productID.value = Number(id)
|
||||||
await fetchForLanguage(selectedLanguage.value)
|
await fetchForLanguage(selectedLanguage.value)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="overflow-x-auto">
|
<div v-else class="overflow-x-auto">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<CategoryMenuListing />
|
<CategoryMenu />
|
||||||
<UTable :data="productsList" :columns="columns" class="flex-1">
|
<UTable :data="productsList" :columns="columns" class="flex-1">
|
||||||
<template #expanded="{ row }">
|
<template #expanded="{ row }">
|
||||||
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
|
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
|
||||||
@@ -46,7 +46,7 @@ import { useFetchJson } from '@/composable/useFetchJson'
|
|||||||
import Default from '@/layouts/default.vue'
|
import Default from '@/layouts/default.vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import type { TableColumn } from '@nuxt/ui'
|
import type { TableColumn } from '@nuxt/ui'
|
||||||
import CategoryMenuListing from '../inner/categoryMenuListing.vue'
|
import CategoryMenu from '../inner/categoryMenu.vue'
|
||||||
|
|
||||||
interface Product {
|
interface Product {
|
||||||
reference: number
|
reference: number
|
||||||
@@ -167,7 +167,7 @@ async function fetchProductList() {
|
|||||||
if (value) params.append(key, String(value))
|
if (value) params.append(key, String(value))
|
||||||
})
|
})
|
||||||
|
|
||||||
const url = `/api/v1/restricted/list-products/get-listing?${params}`
|
const url = `/api/v1/restricted/list/list-products?${params}`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await useFetchJson<ApiResponse>(url)
|
const response = await useFetchJson<ApiResponse>(url)
|
||||||
|
|||||||
@@ -1,20 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<UNavigationMenu orientation="vertical" type="single" :items="items" class="data-[orientation=vertical]:w-72" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getMenu } from '@/router/menu'
|
import { getMenu } from '@/router/menu'
|
||||||
import type { NavigationMenuItem } from '@nuxt/ui';
|
import type { NavigationMenuItem } from '@nuxt/ui';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
let menu = await getMenu() as NavigationMenuItem[]
|
let menu = await getMenu() as NavigationMenuItem[]
|
||||||
|
|
||||||
const openAll = ref(false)
|
const openAll = ref(false)
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
let categoryId = ref(route.params.category_id)
|
||||||
|
function findPath(tree: NavigationMenuItem[], id: number, path: Array<number> = []): Array<number> | null {
|
||||||
|
for (let item of tree) {
|
||||||
|
let newPath: Array<number> = [...path, item.category_id]
|
||||||
|
if (item.category_id === id) {
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
if (item.children) {
|
||||||
|
const result: Array<number> | null = findPath(item.children, id, newPath)
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = findPath(menu, Number(categoryId.value))
|
||||||
function adaptMenu(menu: NavigationMenuItem[]) {
|
function adaptMenu(menu: NavigationMenuItem[]) {
|
||||||
for (const item of menu) {
|
for (const item of menu) {
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
console.log(item);
|
item.open = path && path.includes(item.category_id) ? true : openAll.value
|
||||||
adaptMenu(item.children);
|
adaptMenu(item.children);
|
||||||
item.open = openAll.value
|
item.children.unshift({
|
||||||
item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: { name: 'category', params: item.params } })
|
label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: {
|
||||||
|
name: 'customer-products-category', params: {
|
||||||
|
category_id: item.params.category_id,
|
||||||
|
link_rewrite: item.params.link_rewrite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
item.to = { name: 'category', params: item.params };
|
item.to = {
|
||||||
|
name: 'customer-products-category', params: {
|
||||||
|
category_id: item.params.category_id,
|
||||||
|
link_rewrite: item.params.link_rewrite
|
||||||
|
}
|
||||||
|
};
|
||||||
item.icon = 'i-lucide-file-text'
|
item.icon = 'i-lucide-file-text'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,15 +59,10 @@ function adaptMenu(menu: NavigationMenuItem[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
menu = adaptMenu(menu)
|
menu = adaptMenu(menu)
|
||||||
|
|
||||||
const items = ref<NavigationMenuItem[][]>([
|
const items = ref<NavigationMenuItem[][]>([
|
||||||
[
|
[
|
||||||
...menu as NavigationMenuItem[]
|
...menu as NavigationMenuItem[]
|
||||||
],
|
],
|
||||||
|
|
||||||
])
|
])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<UNavigationMenu orientation="vertical" type="single" :items="items" class="p-4" />
|
|
||||||
</template>
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { getMenu } from '@/router/menu'
|
|
||||||
import type { NavigationMenuItem } from '@nuxt/ui';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
let menu = await getMenu() as NavigationMenuItem[]
|
|
||||||
|
|
||||||
const openAll = ref(false)
|
|
||||||
|
|
||||||
function adaptMenu(menu: NavigationMenuItem[]) {
|
|
||||||
for (const item of menu) {
|
|
||||||
if (item.children && item.children.length > 0) {
|
|
||||||
adaptMenu(item.children);
|
|
||||||
item.open = openAll.value
|
|
||||||
item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: { name: 'category', params: item.params } })
|
|
||||||
} else {
|
|
||||||
item.to = { name: 'category', params: item.params };
|
|
||||||
item.icon = 'i-lucide-file-text'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu = adaptMenu(menu)
|
|
||||||
|
|
||||||
const items = ref<NavigationMenuItem[][]>([
|
|
||||||
[
|
|
||||||
...menu as NavigationMenuItem[]
|
|
||||||
],
|
|
||||||
|
|
||||||
])
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UNavigationMenu orientation="vertical" type="single" :items="items" class="p-4" />
|
|
||||||
</template>
|
|
||||||
@@ -59,7 +59,6 @@ async function setRoutes() {
|
|||||||
const componentName = item.component
|
const componentName = item.component
|
||||||
const [, folder] = componentName.split('/')
|
const [, folder] = componentName.split('/')
|
||||||
const componentPath = `/src${componentName}`
|
const componentPath = `/src${componentName}`
|
||||||
console.log(componentPath);
|
|
||||||
|
|
||||||
|
|
||||||
let modules =
|
let modules =
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { useFetchJson } from "@/composable/useFetchJson";
|
import { useFetchJson } from "@/composable/useFetchJson";
|
||||||
import type { MenuItem, Route } from "@/types/menu";
|
import type { MenuItem, Route } from "@/types/menu";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { settings } from "./settings";
|
||||||
|
|
||||||
|
|
||||||
|
const categoryId = ref()
|
||||||
export const getMenu = async () => {
|
export const getMenu = async () => {
|
||||||
const resp = await useFetchJson<MenuItem>('/api/v1/restricted/menu/get-menu');
|
if(!categoryId.value){
|
||||||
|
categoryId.value = settings['app'].category_tree_root_id
|
||||||
|
}
|
||||||
|
const resp = await useFetchJson<MenuItem>(`/api/v1/restricted/menu/get-category-tree?root_category_id=${categoryId.value}`);
|
||||||
return resp.items.children
|
return resp.items.children
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getRoutes = async () => {
|
export const getRoutes = async () => {
|
||||||
const resp = await useFetchJson<Route[]>('/api/v1/public/menu/get-routes');
|
const resp = await useFetchJson<Route[]>('/api/v1/public/menu/get-routes');
|
||||||
|
|
||||||
|
|||||||
@@ -13,24 +13,24 @@ const products = [
|
|||||||
// type CategoryProducts = {}
|
// type CategoryProducts = {}
|
||||||
|
|
||||||
export const useCategoryStore = defineStore('category', () => {
|
export const useCategoryStore = defineStore('category', () => {
|
||||||
const id_category = ref(0)
|
const idCategory = ref(0)
|
||||||
const categoryProducts = ref(products)
|
const categoryProducts = ref(products)
|
||||||
|
|
||||||
|
|
||||||
function setCategoryID(id: number) {
|
function setCategoryID(id: number) {
|
||||||
id_category.value = id
|
idCategory.value = id
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCategoryProducts() {
|
async function getCategoryProducts() {
|
||||||
return new Promise<typeof products>((resolve) => {
|
return new Promise<typeof products>((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('Fetching products from category id: ', id_category.value);
|
// console.log('Fetching products from category id: ', idCategory.value);
|
||||||
resolve(categoryProducts.value)
|
resolve(categoryProducts.value)
|
||||||
}, 2000 * Math.random())
|
}, 2000 * Math.random())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id_category,
|
idCategory,
|
||||||
getCategoryProducts,
|
getCategoryProducts,
|
||||||
setCategoryID
|
setCategoryID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,8 @@ export const useProductStore = defineStore('product', () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await useFetchJson<ProductDescription>(
|
const response = await useFetchJson<ProductDescription>(
|
||||||
`/api/v1/restricted/product-description/get-product-description?productID=${productID}&productLangID=${langId}`
|
`/api/v1/restricted/product-translation/get-product-description?productID=${productID}&productLangID=${langId}`
|
||||||
)
|
)
|
||||||
console.log(response, 'dfsfsdf');
|
|
||||||
productDescription.value = response.items
|
productDescription.value = response.items
|
||||||
|
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
|||||||
10
bo/src/types/product.d.ts
vendored
10
bo/src/types/product.d.ts
vendored
@@ -1,4 +1,4 @@
|
|||||||
export interface ProductDescription {
|
export interface ProductDescription {
|
||||||
id?: number
|
id?: number
|
||||||
name?: string
|
name?: string
|
||||||
description: string
|
description: string
|
||||||
@@ -6,4 +6,12 @@
|
|||||||
meta_description: string
|
meta_description: string
|
||||||
available_now: string
|
available_now: string
|
||||||
usage: string
|
usage: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Product {
|
||||||
|
reference: number
|
||||||
|
product_id: number
|
||||||
|
name: string
|
||||||
|
image_link: string
|
||||||
|
link_rewrite: string
|
||||||
}
|
}
|
||||||
1
bo/src/types/settings.d.ts
vendored
1
bo/src/types/settings.d.ts
vendored
@@ -7,6 +7,7 @@ export interface Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface App {
|
export interface App {
|
||||||
|
category_tree_root_id: number
|
||||||
name: string
|
name: string
|
||||||
environment: string
|
environment: string
|
||||||
base_url: string
|
base_url: string
|
||||||
|
|||||||
28
bruno/b2b-daniel/save-product-description.yml
Normal file
28
bruno/b2b-daniel/save-product-description.yml
Normal 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
|
||||||
28
bruno/b2b-daniel/translate-product-description.yml
Normal file
28
bruno/b2b-daniel/translate-product-description.yml
Normal 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
|
||||||
7
bruno/b2b_daniel/auth/folder.yml
Normal file
7
bruno/b2b_daniel/auth/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: auth
|
||||||
|
type: folder
|
||||||
|
seq: 1
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: update-choice
|
name: update-choice
|
||||||
type: http
|
type: http
|
||||||
seq: 3
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: POST
|
method: POST
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: add-new-cart
|
name: add-new-cart
|
||||||
type: http
|
type: http
|
||||||
seq: 11
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: add-product-to-cart (1)
|
name: add-product-to-cart (1)
|
||||||
type: http
|
type: http
|
||||||
seq: 16
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: add-product-to-cart
|
name: add-product-to-cart
|
||||||
type: http
|
type: http
|
||||||
seq: 15
|
seq: 14
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: change-cart-name
|
name: change-cart-name
|
||||||
type: http
|
type: http
|
||||||
seq: 12
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
7
bruno/b2b_daniel/carts/folder.yml
Normal file
7
bruno/b2b_daniel/carts/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: carts
|
||||||
|
type: folder
|
||||||
|
seq: 7
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: retrieve-cart
|
name: retrieve-cart
|
||||||
type: http
|
type: http
|
||||||
seq: 14
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: retrieve-carts-info
|
name: retrieve-carts-info
|
||||||
type: http
|
type: http
|
||||||
seq: 13
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
7
bruno/b2b_daniel/langs-and-countries/folder.yml
Normal file
7
bruno/b2b_daniel/langs-and-countries/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: langs-and-countries
|
||||||
|
type: folder
|
||||||
|
seq: 4
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: get_countries
|
name: get_countries
|
||||||
type: http
|
type: http
|
||||||
seq: 4
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
7
bruno/b2b_daniel/list/folder.yml
Normal file
7
bruno/b2b_daniel/list/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: list
|
||||||
|
type: folder
|
||||||
|
seq: 3
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
@@ -5,7 +5,7 @@ info:
|
|||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
url: http://localhost:3000/api/v1/restricted/list/list-products?p=1&elems=10
|
url: http://localhost:3000/api/v1/restricted/list/list-products?p=1&elems=10&target_user_id=2
|
||||||
params:
|
params:
|
||||||
- name: p
|
- name: p
|
||||||
value: "1"
|
value: "1"
|
||||||
@@ -13,6 +13,9 @@ http:
|
|||||||
- name: elems
|
- name: elems
|
||||||
value: "10"
|
value: "10"
|
||||||
type: query
|
type: query
|
||||||
|
- name: target_user_id
|
||||||
|
value: "2"
|
||||||
|
type: query
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
encodeUrl: true
|
encodeUrl: true
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: list-users
|
name: list-users
|
||||||
type: http
|
type: http
|
||||||
seq: 2
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
7
bruno/b2b_daniel/menu/folder.yml
Normal file
7
bruno/b2b_daniel/menu/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: menu
|
||||||
|
type: folder
|
||||||
|
seq: 5
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: get-breadcrumb
|
name: get-breadcrumb
|
||||||
type: http
|
type: http
|
||||||
seq: 18
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: get-category-tree
|
name: get-category-tree
|
||||||
type: http
|
type: http
|
||||||
seq: 5
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
7
bruno/b2b_daniel/product-translation/folder.yml
Normal file
7
bruno/b2b_daniel/product-translation/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: product-translation
|
||||||
|
type: folder
|
||||||
|
seq: 2
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
info:
|
info:
|
||||||
name: get-product-description
|
name: get-product-description
|
||||||
type: http
|
type: http
|
||||||
seq: 17
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
url: http://localhost:3000/api/v1/restricted/product-translation/get-product-description?productID=51&productLangID=4
|
url: http://localhost:3000/api/v1/restricted/product-translation/get-product-description?productID=51&productLangID=1
|
||||||
params:
|
params:
|
||||||
- name: productID
|
- name: productID
|
||||||
value: "51"
|
value: "51"
|
||||||
type: query
|
type: query
|
||||||
- name: productLangID
|
- name: productLangID
|
||||||
value: "4"
|
value: "1"
|
||||||
type: query
|
type: query
|
||||||
auth: inherit
|
auth: inherit
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -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
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
info:
|
info:
|
||||||
name: create-index
|
name: create-index
|
||||||
type: http
|
type: http
|
||||||
seq: 7
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
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
|
auth: inherit
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
7
bruno/b2b_daniel/search/folder.yml
Normal file
7
bruno/b2b_daniel/search/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: search
|
||||||
|
type: folder
|
||||||
|
seq: 6
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: get-indexes
|
name: get-indexes
|
||||||
type: http
|
type: http
|
||||||
seq: 9
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: remove-index
|
name: remove-index
|
||||||
type: http
|
type: http
|
||||||
seq: 8
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: DELETE
|
method: DELETE
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
info:
|
info:
|
||||||
name: search
|
name: search
|
||||||
type: http
|
type: http
|
||||||
seq: 10
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
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:
|
params:
|
||||||
- name: query
|
- name: query
|
||||||
value: w
|
value: w
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
info:
|
info:
|
||||||
name: test
|
name: test
|
||||||
type: http
|
type: http
|
||||||
seq: 6
|
seq: 1
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
url: http://localhost:3000/api/v1/restricted/meili-search/test
|
url: http://localhost:3000/api/v1/restricted/search/test
|
||||||
auth: inherit
|
auth: inherit
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
19
bruno/b2b_daniel/storage-old/copy.yml
Normal file
19
bruno/b2b_daniel/storage-old/copy.yml
Normal 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
|
||||||
19
bruno/b2b_daniel/storage-old/create-folder.yml
Normal file
19
bruno/b2b_daniel/storage-old/create-folder.yml
Normal 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
|
||||||
15
bruno/b2b_daniel/storage-old/delete-file.yml
Normal file
15
bruno/b2b_daniel/storage-old/delete-file.yml
Normal 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
|
||||||
15
bruno/b2b_daniel/storage-old/delete-folder.yml
Normal file
15
bruno/b2b_daniel/storage-old/delete-folder.yml
Normal 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
|
||||||
15
bruno/b2b_daniel/storage-old/download-file.yml
Normal file
15
bruno/b2b_daniel/storage-old/download-file.yml
Normal 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
|
||||||
7
bruno/b2b_daniel/storage-old/folder.yml
Normal file
7
bruno/b2b_daniel/storage-old/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: storage-old
|
||||||
|
type: folder
|
||||||
|
seq: 1
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
15
bruno/b2b_daniel/storage-old/list-content.yml
Normal file
15
bruno/b2b_daniel/storage-old/list-content.yml
Normal 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
|
||||||
19
bruno/b2b_daniel/storage-old/move.yml
Normal file
19
bruno/b2b_daniel/storage-old/move.yml
Normal 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
|
||||||
22
bruno/b2b_daniel/storage-old/upload-file.yml
Normal file
22
bruno/b2b_daniel/storage-old/upload-file.yml
Normal 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,0 +1,15 @@
|
|||||||
|
info:
|
||||||
|
name: create-new-webdav-token
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/storage/create-new-webdav-token
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
7
bruno/b2b_daniel/storage-restricted/folder.yml
Normal file
7
bruno/b2b_daniel/storage-restricted/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: storage-restricted
|
||||||
|
type: folder
|
||||||
|
seq: 9
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
4
go.mod
4
go.mod
@@ -36,6 +36,8 @@ require (
|
|||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
github.com/tidwall/gjson v1.18.0 // indirect
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
@@ -98,7 +100,7 @@ require (
|
|||||||
github.com/valyala/fasthttp v1.69.0 // indirect
|
github.com/valyala/fasthttp v1.69.0 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
github.com/xyproto/randomstring v1.2.0 // indirect
|
github.com/xyproto/randomstring v1.2.0 // indirect
|
||||||
golang.org/x/net v0.52.0 // indirect
|
golang.org/x/net v0.52.0
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.35.0 // indirect
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -72,6 +72,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.12 h1:0LdToKclcPOj8PktUdIKo9BUohjjwfnQl42Dhw8/WUw=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.12/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||||
github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY=
|
github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY=
|
||||||
github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU=
|
github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU=
|
||||||
github.com/gofiber/schema v1.7.0 h1:yNM+FNRZjyYEli9Ey0AXRBrAY9jTnb+kmGs3lJGPvKg=
|
github.com/gofiber/schema v1.7.0 h1:yNM+FNRZjyYEli9Ey0AXRBrAY9jTnb+kmGs3lJGPvKg=
|
||||||
@@ -134,6 +136,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
|||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
@@ -154,6 +158,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ CREATE TABLE IF NOT EXISTS b2b_customers (
|
|||||||
email_verification_expires DATETIME(6) NULL,
|
email_verification_expires DATETIME(6) NULL,
|
||||||
password_reset_token VARCHAR(255) NULL,
|
password_reset_token VARCHAR(255) NULL,
|
||||||
password_reset_expires DATETIME(6) NULL,
|
password_reset_expires DATETIME(6) NULL,
|
||||||
|
webdav_token VARCHAR(255) NULL,
|
||||||
|
webdav_expires DATETIME(6) NULL,
|
||||||
last_password_reset_request DATETIME(6) NULL,
|
last_password_reset_request DATETIME(6) NULL,
|
||||||
last_login_at DATETIME(6) NULL,
|
last_login_at DATETIME(6) NULL,
|
||||||
lang_id BIGINT NULL DEFAULT 2,
|
lang_id BIGINT NULL DEFAULT 2,
|
||||||
@@ -84,6 +86,8 @@ ON b2b_customers (email);
|
|||||||
CREATE INDEX IF NOT EXISTS idx_customers_deleted_at
|
CREATE INDEX IF NOT EXISTS idx_customers_deleted_at
|
||||||
ON b2b_customers (deleted_at);
|
ON b2b_customers (deleted_at);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_customers_webdav_token
|
||||||
|
ON b2b_customers (webdav_token);
|
||||||
|
|
||||||
-- customer_carts
|
-- customer_carts
|
||||||
CREATE TABLE IF NOT EXISTS b2b_customer_carts (
|
CREATE TABLE IF NOT EXISTS b2b_customer_carts (
|
||||||
|
|||||||
0
storage/.gitkeep
Normal file
0
storage/.gitkeep
Normal file
Reference in New Issue
Block a user