Compare commits
85 Commits
fb910b9ac6
...
expand_car
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0a86febc4 | ||
|
|
40154ec861 | ||
|
|
bb507036db | ||
| 80a1314dc0 | |||
|
|
100a9f57d4 | ||
| 773e7d3c20 | |||
| 03a0e5ea64 | |||
| ce8c19f715 | |||
| 4edcb0a852 | |||
| a4120dafa2 | |||
| 5e1a8e898c | |||
|
|
c610ce38cc | ||
| 8e3e41d6fe | |||
| b33da9d072 | |||
|
|
604247b7c8 | ||
| e5988a85f3 | |||
|
|
0cb5cc47bb | ||
|
|
1efc5417be | ||
|
|
a0c3dd8ec8 | ||
| ab783b599d | |||
| d173af29fe | |||
| f14d60d67b | |||
| 967b101f9b | |||
| 97ca510b99 | |||
| 26cbdeec0a | |||
|
|
ce4cadaa16 | ||
|
|
7f05d39b38 | ||
| 83b7cd49dd | |||
|
|
88255776f3 | ||
| 38cb07f3d4 | |||
|
|
1f6d5ecb72 | ||
| 2e61cde742 | |||
|
|
d4d55e2757 | ||
| 54608410ea | |||
|
|
80d26bba12 | ||
|
|
33e9d016e9 | ||
|
|
a03a2b461f | ||
|
|
134bc4ea53 | ||
|
|
8595969c6e | ||
|
|
a6aa06faa0 | ||
|
|
4f4b32b131 | ||
|
|
dfdf8b4db9 | ||
|
|
438a13c04c | ||
| 8024d9f739 | |||
|
|
c5832c0cf5 | ||
|
|
61ccd32c4a | ||
|
|
f7f56c2928 | ||
|
|
0a5ce5d9c2 | ||
|
|
f1f5daa82b | ||
|
|
393de36cb2 | ||
| 9fb8e034fc | |||
|
|
1083ab7a61 | ||
| 75af44b0df | |||
| 75997ab15b | |||
|
|
569a805a13 | ||
|
|
578d8c6cac | ||
|
|
cbd0baaa50 | ||
|
|
7eee0bd032 | ||
| 92ba9c5f07 | |||
| a121ddc246 | |||
| d56650ae5d | |||
| 1a6311dc3d | |||
| 2e645f3368 | |||
| de3f2d1777 | |||
| 9187297367 | |||
| 813d1f4879 | |||
| c5cc4f7a48 | |||
| 76ca2a2eed | |||
| 84388792f0 | |||
|
|
7264a11ba6 | ||
| 61dc240c38 | |||
|
|
f6b321b602 | ||
|
|
af91842b14 | ||
| 04e238fd66 | |||
|
|
e0c53c97ba | ||
|
|
1bab7f642f | ||
|
|
a988bbbc33 | ||
|
|
395d670298 | ||
|
|
7d4242abb1 | ||
|
|
9c7eb5ee4e | ||
|
|
833f4a5a07 | ||
|
|
b9bc121d43 | ||
|
|
b2acb8c922 | ||
|
|
03f04b2f53 | ||
|
|
55da953f32 |
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
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,3 +6,5 @@ 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,20 +1,24 @@
|
|||||||
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/delivery/middleware/perms"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AuthMiddleware creates authentication middleware
|
// AuthMiddleware creates authentication middleware
|
||||||
func AuthMiddleware() fiber.Handler {
|
func Authenticate() fiber.Handler {
|
||||||
authService := authService.NewAuthService()
|
authService := authService.NewAuthService()
|
||||||
|
|
||||||
return func(c fiber.Ctx) error {
|
return func(c fiber.Ctx) error {
|
||||||
// Get token from Authorization header
|
// Get token from Authorization header
|
||||||
authHeader := c.Get("Authorization")
|
authHeader := c.Get("Authorization")
|
||||||
@@ -22,17 +26,13 @@ func AuthMiddleware() fiber.Handler {
|
|||||||
// Try to get from cookie
|
// Try to get from cookie
|
||||||
authHeader = c.Cookies("access_token")
|
authHeader = c.Cookies("access_token")
|
||||||
if authHeader == "" {
|
if authHeader == "" {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
return c.Next()
|
||||||
"error": "authorization token required",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Extract token from "Bearer <token>"
|
// Extract token from "Bearer <token>"
|
||||||
parts := strings.Split(authHeader, " ")
|
parts := strings.Split(authHeader, " ")
|
||||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
return c.Next()
|
||||||
"error": "invalid authorization header format",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
authHeader = parts[1]
|
authHeader = parts[1]
|
||||||
}
|
}
|
||||||
@@ -40,79 +40,147 @@ func AuthMiddleware() fiber.Handler {
|
|||||||
// Validate token
|
// Validate token
|
||||||
claims, err := authService.ValidateToken(authHeader)
|
claims, err := authService.ValidateToken(authHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
return c.Next()
|
||||||
"error": "invalid or expired token",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user from database
|
// Get user from database
|
||||||
user, err := authService.GetUserByID(claims.UserID)
|
user, err := authService.GetUserByID(claims.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is active
|
||||||
|
if !user.IsActive {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create locale. LangID is overwritten by auth Token
|
||||||
|
var userLocale model.UserLocale
|
||||||
|
userLocale.OriginalUser = user
|
||||||
|
|
||||||
|
// Check if target user is present
|
||||||
|
targetUserIDAttribute := c.Query("target_user_id")
|
||||||
|
|
||||||
|
if targetUserIDAttribute == "" {
|
||||||
|
userLocale.User = user
|
||||||
|
c.Locals(constdata.USER_LOCALE, &userLocale)
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now populate the target user
|
||||||
|
if !userLocale.OriginalUser.HasPermission(perms.Teleport) {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Authorize() fiber.Handler {
|
||||||
|
return func(c fiber.Ctx) error {
|
||||||
|
_, ok := localeExtractor.GetUserID(c)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"error": "not authenticated",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webdav
|
||||||
|
func Webdav() fiber.Handler {
|
||||||
|
authService := authService.NewAuthService()
|
||||||
|
|
||||||
|
return func(c fiber.Ctx) error {
|
||||||
|
authHeader := c.Get("Authorization")
|
||||||
|
if authHeader == "" {
|
||||||
|
c.Set("WWW-Authenticate", `Basic realm="webdav"`)
|
||||||
|
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 {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
"error": "user not found",
|
"error": "user not found",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user is active
|
if user.WebdavExpires != nil && user.WebdavExpires.Before(time.Now()) {
|
||||||
if !user.IsActive {
|
|
||||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
|
||||||
"error": "user account is inactive",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set user in context
|
|
||||||
c.Locals(constdata.USER_LOCALES_NAME, user.ToSession())
|
|
||||||
c.Locals(constdata.USER_LOCALES_ID, user.ID)
|
|
||||||
c.Locals(constdata.LANG_LOCALES_ID, user.LangID)
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequireAdmin creates admin-only middleware
|
|
||||||
func RequireAdmin() fiber.Handler {
|
|
||||||
return func(c fiber.Ctx) error {
|
|
||||||
user := c.Locals("user")
|
|
||||||
if user == nil {
|
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
"error": "not authenticated",
|
"error": "invalid or expired token",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
userSession, ok := user.(*model.UserSession)
|
var userLocale model.UserLocale
|
||||||
if !ok {
|
userLocale.OriginalUser = user
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
userLocale.User = user
|
||||||
"error": "invalid user session",
|
c.Locals(constdata.USER_LOCALE, &userLocale)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if model.CustomerRole(userSession.RoleName) != model.RoleAdmin {
|
|
||||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
|
|
||||||
"error": "admin access required",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserID extracts user ID from context
|
|
||||||
func GetUserID(c fiber.Ctx) uint {
|
|
||||||
userID, ok := c.Locals("userID").(uint)
|
|
||||||
if !ok {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return userID
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser extracts user from context
|
|
||||||
func GetUser(c fiber.Ctx) *model.UserSession {
|
|
||||||
user, ok := c.Locals("user").(*model.UserSession)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConfig returns the app config
|
// GetConfig returns the app config
|
||||||
func GetConfig() *config.Config {
|
func GetConfig() *config.Config {
|
||||||
return config.Get()
|
return config.Get()
|
||||||
|
|||||||
@@ -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,15 +24,11 @@ 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 {
|
|
||||||
c.Locals("langID", langID)
|
|
||||||
c.Locals("lang", lang)
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Check cookie
|
// 2. Check cookie
|
||||||
cookieLang := c.Cookies("lang_id", "")
|
cookieLang := c.Cookies("lang_id", "")
|
||||||
@@ -38,15 +36,11 @@ 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 {
|
|
||||||
c.Locals("langID", langID)
|
|
||||||
c.Locals("lang", lang)
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Check Accept-Language header
|
// 3. Check Accept-Language header
|
||||||
acceptLang := c.Get("Accept-Language", "")
|
acceptLang := c.Get("Accept-Language", "")
|
||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,27 +2,27 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"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"
|
"github.com/gofiber/fiber/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Require(p perms.Permission) fiber.Handler {
|
func Require(p perms.Permission) fiber.Handler {
|
||||||
return func(c fiber.Ctx) error {
|
return func(c fiber.Ctx) error {
|
||||||
u := c.Locals("user")
|
user, ok := localeExtractor.GetCustomer(c)
|
||||||
if u == nil {
|
|
||||||
return c.SendStatus(fiber.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
user, ok := u.(*model.UserSession)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, perm := range user.Permissions {
|
for _, perm := range user.Role.Permissions {
|
||||||
if perm == p {
|
if perm.Name == p {
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.SendStatus(fiber.StatusForbidden)
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrForbidden)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrForbidden)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,16 @@ package perms
|
|||||||
type Permission string
|
type Permission string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UserRead Permission = "user.read"
|
|
||||||
UserWrite Permission = "user.write"
|
|
||||||
UserReadAny Permission = "user.read.any"
|
UserReadAny Permission = "user.read.any"
|
||||||
UserWriteAny Permission = "user.write.any"
|
UserWriteAny Permission = "user.write.any"
|
||||||
UserDeleteAny Permission = "user.delete.any"
|
UserDeleteAny Permission = "user.delete.any"
|
||||||
|
CurrencyWrite Permission = "currency.write"
|
||||||
|
SpecificPriceManage Permission = "specific_price.manage"
|
||||||
|
WebdavCreateToken Permission = "webdav.create_token"
|
||||||
|
ProductTranslationSave Permission = "product_translation.save"
|
||||||
|
ProductTranslationTranslate Permission = "product_translation.translate"
|
||||||
|
SearchCreateIndex Permission = "search.create_index"
|
||||||
|
OrdersViewAll Permission = "orders.view_all"
|
||||||
|
OrdersModifyAll Permission = "orders.modify_all"
|
||||||
|
Teleport Permission = "teleport"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func AuthHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
r.Get("/google", handler.GoogleLogin)
|
r.Get("/google", handler.GoogleLogin)
|
||||||
r.Get("/google/callback", handler.GoogleCallback)
|
r.Get("/google/callback", handler.GoogleCallback)
|
||||||
|
|
||||||
authProtected := r.Group("", middleware.AuthMiddleware())
|
authProtected := r.Group("", middleware.Authorize())
|
||||||
authProtected.Get("/me", handler.Me)
|
authProtected.Get("/me", handler.Me)
|
||||||
authProtected.Post("/update-choice", handler.UpdateJWTToken)
|
authProtected.Post("/update-choice", handler.UpdateJWTToken)
|
||||||
|
|
||||||
@@ -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: model.Role{ID: userLocals.RoleID, Name: userLocals.RoleName},
|
|
||||||
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{
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package public
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
|
||||||
|
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/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,12 +32,21 @@ 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)
|
langId, ok := localeExtractor.GetLangID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
}
|
}
|
||||||
menu, err := h.menuService.GetRoutes(lang_id)
|
|
||||||
|
var roleId uint
|
||||||
|
customer, ok := localeExtractor.GetCustomer(c)
|
||||||
|
if !ok {
|
||||||
|
roleId = constdata.UNLOGGED_USER_ROLE_ID
|
||||||
|
} else {
|
||||||
|
roleId = customer.RoleID
|
||||||
|
}
|
||||||
|
|
||||||
|
menu, err := h.menuService.GetRoutes(langId, roleId)
|
||||||
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)))
|
||||||
|
|||||||
157
app/delivery/web/api/restricted/addresses.go
Normal file
157
app/delivery/web/api/restricted/addresses.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package restricted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/service/addressesService"
|
||||||
|
"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 AddressesHandler struct {
|
||||||
|
addressesService *addressesService.AddressesService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddressesHandler() *AddressesHandler {
|
||||||
|
addressesService := addressesService.New()
|
||||||
|
return &AddressesHandler{
|
||||||
|
addressesService: addressesService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddressesHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
|
handler := NewAddressesHandler()
|
||||||
|
|
||||||
|
r.Get("/get-template", handler.GetTemplate)
|
||||||
|
r.Post("/add-new-address", handler.AddNewAddress)
|
||||||
|
r.Post("/modify-address", handler.ModifyAddress)
|
||||||
|
r.Get("/retrieve-addresses", handler.RetrieveAddressesInfo)
|
||||||
|
r.Delete("/delete-address", handler.DeleteAddress)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AddressesHandler) GetTemplate(c fiber.Ctx) error {
|
||||||
|
country_id_attribute := c.Query("country_id")
|
||||||
|
country_id, err := strconv.Atoi(country_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
template, err := h.addressesService.GetTemplate(uint(country_id))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&template, 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AddressesHandler) AddNewAddress(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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
address_info := string(c.Body())
|
||||||
|
if address_info == "" {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
country_id_attribute := c.Query("country_id")
|
||||||
|
country_id, err := strconv.Atoi(country_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.addressesService.AddNewAddress(userID, address_info, uint(country_id))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AddressesHandler) ModifyAddress(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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
address_id_attribute := c.Query("address_id")
|
||||||
|
address_id, err := strconv.Atoi(address_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
address_info := string(c.Body())
|
||||||
|
if address_info == "" {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
country_id_attribute := c.Query("country_id")
|
||||||
|
country_id, err := strconv.Atoi(country_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.addressesService.ModifyAddress(userID, uint(address_id), address_info, uint(country_id))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AddressesHandler) RetrieveAddressesInfo(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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses, err := h.addressesService.RetrieveAddresses(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(addresses, 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AddressesHandler) DeleteAddress(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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
address_id_attribute := c.Query("address_id")
|
||||||
|
address_id, err := strconv.Atoi(address_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.addressesService.DeleteAddress(userID, uint(address_id))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
@@ -28,16 +29,18 @@ func CartsHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
handler := NewCartsHandler()
|
handler := NewCartsHandler()
|
||||||
|
|
||||||
r.Get("/add-new-cart", handler.AddNewCart)
|
r.Get("/add-new-cart", handler.AddNewCart)
|
||||||
|
r.Delete("/remove-cart", handler.RemoveCart)
|
||||||
r.Get("/change-cart-name", handler.ChangeCartName)
|
r.Get("/change-cart-name", handler.ChangeCartName)
|
||||||
r.Get("/retrieve-carts-info", handler.RetrieveCartsInfo)
|
r.Get("/retrieve-carts-info", handler.RetrieveCartsInfo)
|
||||||
r.Get("/retrieve-cart", handler.RetrieveCart)
|
r.Get("/retrieve-cart", handler.RetrieveCart)
|
||||||
r.Get("/add-product-to-cart", handler.AddProduct)
|
r.Get("/add-product-to-cart", handler.AddProduct)
|
||||||
|
r.Delete("/remove-product-from-cart", handler.RemoveProduct)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
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)))
|
||||||
@@ -52,8 +55,31 @@ func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
|
|||||||
return c.JSON(response.Make(&new_cart, 0, i18n.T_(c, response.Message_OK)))
|
return c.JSON(response.Make(&new_cart, 0, i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *CartsHandler) RemoveCart(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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cart_id_attribute := c.Query("cart_id")
|
||||||
|
cart_id, err := strconv.Atoi(cart_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.cartsService.RemoveCart(userID, uint(cart_id))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
func (h *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 +104,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 +120,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)))
|
||||||
@@ -116,8 +142,9 @@ func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
|
|||||||
return c.JSON(response.Make(cart, 0, i18n.T_(c, response.Message_OK)))
|
return c.JSON(response.Make(cart, 0, i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adds or sets given amount of products to the cart
|
||||||
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)))
|
||||||
@@ -158,7 +185,59 @@ func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.cartsService.AddProduct(userID, uint(cart_id), uint(product_id), product_attribute_id, uint(amount))
|
set_amount_attribute := c.Query("set_amount")
|
||||||
|
set_amount, err := strconv.ParseBool(set_amount_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.cartsService.AddProduct(userID, uint(cart_id), uint(product_id), product_attribute_id, amount, set_amount)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// removes product from the cart.
|
||||||
|
func (h *CartsHandler) RemoveProduct(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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cart_id_attribute := c.Query("cart_id")
|
||||||
|
cart_id, err := strconv.Atoi(cart_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
product_id_attribute := c.Query("product_id")
|
||||||
|
product_id, err := strconv.Atoi(product_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
product_attribute_id_attribute := c.Query("product_attribute_id")
|
||||||
|
var product_attribute_id *uint
|
||||||
|
if product_attribute_id_attribute == "" {
|
||||||
|
product_attribute_id = nil
|
||||||
|
} else {
|
||||||
|
val, err := strconv.Atoi(product_attribute_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
uval := uint(val)
|
||||||
|
product_attribute_id = &uval
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.cartsService.RemoveProduct(userID, uint(cart_id), uint(product_id), product_attribute_id)
|
||||||
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)))
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ 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/delivery/middleware"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||||
"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/currencyService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/currencyService"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
@@ -30,7 +32,7 @@ func NewCurrencyHandler() *CurrencyHandler {
|
|||||||
func CurrencyHandlerRoutes(r fiber.Router) fiber.Router {
|
func CurrencyHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
handler := NewCurrencyHandler()
|
handler := NewCurrencyHandler()
|
||||||
|
|
||||||
r.Post("/currency-rate", handler.PostCurrencyRate)
|
r.Post("/currency-rate", middleware.Require(perms.CurrencyWrite), handler.PostCurrencyRate)
|
||||||
r.Get("/currency-rate/:id", handler.GetCurrencyRate)
|
r.Get("/currency-rate/:id", handler.GetCurrencyRate)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ package restricted
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||||
"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/customerService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/customerService"
|
||||||
"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/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"
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
@@ -28,36 +31,34 @@ func CustomerHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
handler := NewCustomerHandler()
|
handler := NewCustomerHandler()
|
||||||
|
|
||||||
r.Get("", handler.customerData)
|
r.Get("", handler.customerData)
|
||||||
|
r.Get("/list", middleware.Require(perms.UserReadAny), handler.listCustomers)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *customerHandler) customerData(fc fiber.Ctx) error {
|
func (h *customerHandler) customerData(fc fiber.Ctx) error {
|
||||||
var customerId uint
|
var customerId uint
|
||||||
customerIdStr := fc.Query("id")
|
|
||||||
if customerIdStr != "" {
|
user, ok := localeExtractor.GetCustomer(fc)
|
||||||
user, ok := fc.Locals("user").(*model.UserSession)
|
if !ok || user == nil {
|
||||||
if !ok {
|
|
||||||
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
customerIdStr := fc.Query("id")
|
||||||
|
if customerIdStr != "" {
|
||||||
id, err := strconv.ParseUint(customerIdStr, 10, 64)
|
id, err := strconv.ParseUint(customerIdStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.ErrBadRequest
|
return fiber.ErrBadRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.UserID != uint(id) && !user.HasPermission(perms.UserReadAny) {
|
if user.ID != uint(id) && !user.HasPermission(perms.UserReadAny) {
|
||||||
return fc.Status(fiber.StatusForbidden).
|
return fc.Status(fiber.StatusForbidden).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrForbidden)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrForbidden)))
|
||||||
}
|
}
|
||||||
|
|
||||||
customerId = uint(id)
|
customerId = uint(id)
|
||||||
} else {
|
} else {
|
||||||
id, ok := fc.Locals("userID").(uint)
|
customerId = user.ID
|
||||||
if !ok {
|
|
||||||
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute)))
|
|
||||||
}
|
|
||||||
customerId = id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
customer, err := h.service.GetById(customerId)
|
customer, err := h.service.GetById(customerId)
|
||||||
@@ -68,3 +69,34 @@ func (h *customerHandler) customerData(fc fiber.Ctx) error {
|
|||||||
|
|
||||||
return fc.JSON(response.Make(&customer, 0, i18n.T_(fc, response.Message_OK)))
|
return fc.JSON(response.Make(&customer, 0, i18n.T_(fc, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *customerHandler) listCustomers(fc fiber.Ctx) error {
|
||||||
|
user, ok := localeExtractor.GetCustomer(fc)
|
||||||
|
if !ok || user == nil {
|
||||||
|
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
p, filt, err := query_params.ParseFilters[model.Customer](fc, columnMappingListUsers)
|
||||||
|
if err != nil {
|
||||||
|
return fc.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
search := fc.Query("search")
|
||||||
|
|
||||||
|
customer, err := h.service.Find(user.LangID, p, filt, search)
|
||||||
|
if err != nil {
|
||||||
|
return fc.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fc.JSON(response.Make(&customer, 0, i18n.T_(fc, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var columnMappingListUsers map[string]string = map[string]string{
|
||||||
|
"user_id": "users.id",
|
||||||
|
"email": "users.email",
|
||||||
|
"first_name": "users.first_name",
|
||||||
|
"last_name": "users.last_name",
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
package restricted
|
|
||||||
|
|
||||||
import (
|
|
||||||
"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/listService"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/query_params"
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListHandler handles endpoints that list various things (e.g. products or users)
|
|
||||||
type ListHandler struct {
|
|
||||||
listService *listService.ListService
|
|
||||||
config *config.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewListHandler creates a new ListHandler instance
|
|
||||||
func NewListHandler() *ListHandler {
|
|
||||||
listService := listService.New()
|
|
||||||
return &ListHandler{
|
|
||||||
listService: listService,
|
|
||||||
config: config.Get(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListHandlerRoutes(r fiber.Router) fiber.Router {
|
|
||||||
handler := NewListHandler()
|
|
||||||
|
|
||||||
r.Get("/list-products", handler.ListProducts)
|
|
||||||
r.Get("/list-users", handler.ListUsers)
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ListHandler) ListProducts(c fiber.Ctx) error {
|
|
||||||
paging, filters, err := query_params.ParseFilters[model.Product](c, columnMappingListProducts)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
|
||||||
}
|
|
||||||
|
|
||||||
id_lang, ok := c.Locals("langID").(uint)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
|
||||||
}
|
|
||||||
|
|
||||||
listing, err := h.listService.ListProducts(id_lang, paging, filters)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK)))
|
|
||||||
}
|
|
||||||
|
|
||||||
var columnMappingListProducts map[string]string = map[string]string{
|
|
||||||
"product_id": "ps.id_product",
|
|
||||||
"name": "pl.name",
|
|
||||||
"reference": "p.reference",
|
|
||||||
"category_name": "cl.name",
|
|
||||||
"category_id": "cp.id_category",
|
|
||||||
"quantity": "sa.quantity",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ListHandler) ListUsers(c fiber.Ctx) error {
|
|
||||||
paging, filters, err := query_params.ParseFilters[model.Customer](c, columnMappingListUsers)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
|
||||||
}
|
|
||||||
|
|
||||||
id_lang, ok := c.Locals("langID").(uint)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
|
||||||
}
|
|
||||||
|
|
||||||
listing, err := h.listService.ListUsers(id_lang, paging, filters)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK)))
|
|
||||||
}
|
|
||||||
|
|
||||||
var columnMappingListUsers map[string]string = map[string]string{
|
|
||||||
"user_id": "users.id",
|
|
||||||
"email": "users.email",
|
|
||||||
"first_name": "users.first_name",
|
|
||||||
"second_name": "users.second_name",
|
|
||||||
"role": "users.role",
|
|
||||||
}
|
|
||||||
@@ -3,10 +3,9 @@ package restricted
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
|
||||||
|
|
||||||
"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"
|
||||||
@@ -35,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)))
|
||||||
@@ -58,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)))
|
||||||
@@ -88,12 +87,12 @@ func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
|
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
|
||||||
session, ok := c.Locals("user").(*model.UserSession)
|
customer, ok := localeExtractor.GetCustomer(c)
|
||||||
if !ok {
|
if !ok || customer == nil {
|
||||||
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)))
|
||||||
}
|
}
|
||||||
menu, err := h.menuService.GetTopMenu(session.LangID, session.RoleID)
|
menu, err := h.menuService.GetTopMenu(customer.LangID, customer.RoleID)
|
||||||
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)))
|
||||||
|
|||||||
171
app/delivery/web/api/restricted/orders.go
Normal file
171
app/delivery/web/api/restricted/orders.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package restricted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/service/orderService"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/query_params"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrdersHandler struct {
|
||||||
|
ordersService *orderService.OrderService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrdersHandler() *OrdersHandler {
|
||||||
|
ordersService := orderService.New()
|
||||||
|
return &OrdersHandler{
|
||||||
|
ordersService: ordersService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func OrdersHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
|
handler := NewOrdersHandler()
|
||||||
|
|
||||||
|
r.Get("/list", handler.ListOrders)
|
||||||
|
r.Post("/place-new-order", handler.PlaceNewOrder)
|
||||||
|
r.Post("/change-order-address", handler.ChangeOrderAddress)
|
||||||
|
r.Get("/change-order-status", handler.ChangeOrderStatus)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// when a user (not admin) wants to list orders, we automatically append filter to only view his orders.
|
||||||
|
// we base permissions and user based on target user only.
|
||||||
|
func (h *OrdersHandler) ListOrders(c fiber.Ctx) error {
|
||||||
|
user, ok := localeExtractor.GetCustomer(c)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
paging, filters, err := query_params.ParseFilters[model.CustomerOrder](c, columnMappingListOrders)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := h.ordersService.Find(user, paging, filters)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&list.Items, int(list.Count), i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var columnMappingListOrders map[string]string = map[string]string{
|
||||||
|
"order_id": "b2b_customer_orders.order_id",
|
||||||
|
"user_id": "b2b_customer_orders.user_id",
|
||||||
|
"name": "b2b_customer_orders.name",
|
||||||
|
"country_id": "b2b_customer_orders.country_id",
|
||||||
|
"status": "b2b_customer_orders.status",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OrdersHandler) PlaceNewOrder(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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cart_id_attribute := c.Query("cart_id")
|
||||||
|
cart_id, err := strconv.Atoi(cart_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
country_id_attribute := c.Query("country_id")
|
||||||
|
country_id, err := strconv.Atoi(country_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
address_info := string(c.Body())
|
||||||
|
if address_info == "" {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
name := c.Query("name")
|
||||||
|
|
||||||
|
err = h.ordersService.PlaceNewOrder(userID, uint(cart_id), name, uint(country_id), address_info)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// we base permissions and user based on target user only.
|
||||||
|
func (h *OrdersHandler) ChangeOrderAddress(c fiber.Ctx) error {
|
||||||
|
user, ok := localeExtractor.GetCustomer(c)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
order_id_attribute := c.Query("order_id")
|
||||||
|
order_id, err := strconv.Atoi(order_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
country_id_attribute := c.Query("country_id")
|
||||||
|
country_id, err := strconv.Atoi(country_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
address_info := string(c.Body())
|
||||||
|
if address_info == "" {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.ordersService.ChangeOrderAddress(user, uint(order_id), uint(country_id), address_info)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// we base permissions and user based on target user only.
|
||||||
|
// TODO: well, permissions and all that.
|
||||||
|
func (h *OrdersHandler) ChangeOrderStatus(c fiber.Ctx) error {
|
||||||
|
user, ok := localeExtractor.GetCustomer(c)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
order_id_attribute := c.Query("order_id")
|
||||||
|
order_id, err := strconv.Atoi(order_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
status := c.Query("status")
|
||||||
|
|
||||||
|
err = h.ordersService.ChangeOrderStatus(user, uint(order_id), status)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
package restricted
|
package restricted
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"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/dbmodel"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/productService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/productService"
|
||||||
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/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/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"
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
@@ -32,6 +34,10 @@ func ProductsHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
handler := NewProductsHandler()
|
handler := NewProductsHandler()
|
||||||
|
|
||||||
r.Get("/:id/:country_id/:quantity", handler.GetProductJson)
|
r.Get("/:id/:country_id/:quantity", handler.GetProductJson)
|
||||||
|
r.Get("/list", handler.ListProducts)
|
||||||
|
r.Get("/list-variants/:product_id", handler.ListProductVariants)
|
||||||
|
r.Post("/favorite/:product_id", handler.AddToFavorites)
|
||||||
|
r.Delete("/favorite/:product_id", handler.RemoveFromFavorites)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -61,18 +67,12 @@ func (h *ProductsHandler) GetProductJson(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
p_id_customer, ok := c.Locals(constdata.USER_LOCALES_ID).(uint)
|
customer, ok := localeExtractor.GetCustomer(c)
|
||||||
if !ok {
|
if !ok || customer == nil {
|
||||||
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)))
|
||||||
}
|
}
|
||||||
fmt.Printf("p_id_customer: %v\n", p_id_customer)
|
productJson, err := h.productService.Get(uint(p_id_product), customer.LangID, customer.ID, uint(b2b_id_country), uint(p_quantity))
|
||||||
id_lang, ok := c.Locals(constdata.LANG_LOCALES_ID).(uint)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
|
||||||
}
|
|
||||||
productJson, err := h.productService.GetJSON(p_id_product, int(id_lang), int(p_id_customer), b2b_id_country, p_quantity)
|
|
||||||
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)))
|
||||||
@@ -80,3 +80,108 @@ func (h *ProductsHandler) GetProductJson(c fiber.Ctx) error {
|
|||||||
|
|
||||||
return c.JSON(response.Make(&productJson, 1, i18n.T_(c, response.Message_OK)))
|
return c.JSON(response.Make(&productJson, 1, i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *ProductsHandler) ListProducts(c fiber.Ctx) error {
|
||||||
|
paging, filters, err := query_params.ParseFilters[dbmodel.PsProduct](c, columnMappingListProducts)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
customer, ok := localeExtractor.GetCustomer(c)
|
||||||
|
if !ok || customer == nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := h.productService.Find(customer.LangID, customer.ID, paging, filters, customer, constdata.DEFAULT_PRODUCT_QUANTITY, constdata.SHOP_ID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&list.Items, int(list.Count), i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are all the filterable fields
|
||||||
|
var columnMappingListProducts map[string]string = map[string]string{
|
||||||
|
"product_id": "bp.product_id",
|
||||||
|
"name": "bp.name",
|
||||||
|
"reference": "bp.reference",
|
||||||
|
"category_id": "bp.category_id",
|
||||||
|
"quantity": "bp.quantity",
|
||||||
|
"is_favorite": "bp.is_favorite",
|
||||||
|
"is_new": "bp.is_new",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ProductsHandler) AddToFavorites(c fiber.Ctx) error {
|
||||||
|
productIDStr := c.Params("product_id")
|
||||||
|
|
||||||
|
productID, err := strconv.Atoi(productIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.productService.AddToFavorites(userID, uint(productID))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ProductsHandler) RemoveFromFavorites(c fiber.Ctx) error {
|
||||||
|
productIDStr := c.Params("product_id")
|
||||||
|
|
||||||
|
productID, err := strconv.Atoi(productIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.productService.RemoveFromFavorites(userID, uint(productID))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ProductsHandler) ListProductVariants(c fiber.Ctx) error {
|
||||||
|
productIDStr := c.Params("product_id")
|
||||||
|
|
||||||
|
productID, err := strconv.Atoi(productIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
customer, ok := localeExtractor.GetCustomer(c)
|
||||||
|
if !ok || customer == nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := h.productService.GetProductAttributes(customer.LangID, uint(productID), constdata.SHOP_ID, customer.ID, customer.CountryID, constdata.DEFAULT_PRODUCT_QUANTITY)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&list, len(list), i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ 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/delivery/middleware"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||||
"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"
|
||||||
@@ -33,15 +36,15 @@ func ProductTranslationHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
handler := NewProductTranslationHandler()
|
handler := NewProductTranslationHandler()
|
||||||
|
|
||||||
r.Get("/get-product-description", handler.GetProductDescription)
|
r.Get("/get-product-description", handler.GetProductDescription)
|
||||||
r.Post("/save-product-description", handler.SaveProductDescription)
|
r.Post("/save-product-description", middleware.Require(perms.ProductTranslationSave), handler.SaveProductDescription)
|
||||||
r.Get("/translate-product-description", handler.TranslateProductDescription)
|
r.Get("/translate-product-description", middleware.Require(perms.ProductTranslationTranslate), handler.TranslateProductDescription)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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,7 +75,7 @@ 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)))
|
||||||
@@ -109,7 +112,7 @@ 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)))
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||||
"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"
|
||||||
@@ -28,7 +31,7 @@ func NewMeiliSearchHandler() *MeiliSearchHandler {
|
|||||||
func MeiliSearchHandlerRoutes(r fiber.Router) fiber.Router {
|
func MeiliSearchHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
handler := NewMeiliSearchHandler()
|
handler := NewMeiliSearchHandler()
|
||||||
|
|
||||||
r.Get("/create-index", handler.CreateIndex)
|
r.Get("/create-index", middleware.Require(perms.SearchCreateIndex), handler.CreateIndex)
|
||||||
r.Post("/search", handler.Search)
|
r.Post("/search", handler.Search)
|
||||||
r.Post("/settings", handler.GetSettings)
|
r.Post("/settings", handler.GetSettings)
|
||||||
|
|
||||||
@@ -36,7 +39,7 @@ 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)))
|
||||||
@@ -49,12 +52,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 +90,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)))
|
||||||
|
|||||||
160
app/delivery/web/api/restricted/specificPrice.go
Normal file
160
app/delivery/web/api/restricted/specificPrice.go
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package restricted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/service/specificPriceService"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SpecificPriceHandler struct {
|
||||||
|
SpecificPriceService *specificPriceService.SpecificPriceService
|
||||||
|
config *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSpecificPriceHandler() *SpecificPriceHandler {
|
||||||
|
SpecificPriceService := specificPriceService.New()
|
||||||
|
return &SpecificPriceHandler{
|
||||||
|
SpecificPriceService: SpecificPriceService,
|
||||||
|
config: config.Get(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SpecificPriceHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
|
handler := NewSpecificPriceHandler()
|
||||||
|
|
||||||
|
r.Post("/", middleware.Require(perms.SpecificPriceManage), handler.Create)
|
||||||
|
r.Put("/:id", middleware.Require(perms.SpecificPriceManage), handler.Update)
|
||||||
|
r.Delete("/:id", middleware.Require(perms.SpecificPriceManage), handler.Delete)
|
||||||
|
r.Get("/", middleware.Require(perms.SpecificPriceManage), handler.List)
|
||||||
|
r.Get("/:id", middleware.Require(perms.SpecificPriceManage), handler.GetByID)
|
||||||
|
r.Patch("/:id/activate", middleware.Require(perms.SpecificPriceManage), handler.Activate)
|
||||||
|
r.Patch("/:id/deactivate", middleware.Require(perms.SpecificPriceManage), handler.Deactivate)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpecificPriceHandler) Create(c fiber.Ctx) error {
|
||||||
|
var pr model.SpecificPrice
|
||||||
|
if err := c.Bind().Body(&pr); err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.SpecificPriceService.Create(c.Context(), &pr)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&result, 1, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpecificPriceHandler) Update(c fiber.Ctx) error {
|
||||||
|
idStr := c.Params("id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var pr model.SpecificPrice
|
||||||
|
if err := c.Bind().Body(&pr); err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.SpecificPriceService.Update(c.Context(), id, &pr)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&result, 1, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpecificPriceHandler) List(c fiber.Ctx) error {
|
||||||
|
result, err := h.SpecificPriceService.List(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&result, 1, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpecificPriceHandler) GetByID(c fiber.Ctx) error {
|
||||||
|
idStr := c.Params("id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.SpecificPriceService.GetByID(c.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&result, 1, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpecificPriceHandler) Activate(c fiber.Ctx) error {
|
||||||
|
idStr := c.Params("id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.SpecificPriceService.SetActive(c.Context(), id, true)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpecificPriceHandler) Deactivate(c fiber.Ctx) error {
|
||||||
|
idStr := c.Params("id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.SpecificPriceService.SetActive(c.Context(), id, false)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpecificPriceHandler) Delete(c fiber.Ctx) error {
|
||||||
|
idStr := c.Params("id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.SpecificPriceService.Delete(c.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
95
app/delivery/web/api/restricted/storage.go
Normal file
95
app/delivery/web/api/restricted/storage.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package restricted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||||
|
"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", middleware.Require(perms.WebdavCreateToken), 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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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{
|
|
||||||
|
app :=
|
||||||
|
fiber.New(fiber.Config{
|
||||||
ErrorHandler: customErrorHandler,
|
ErrorHandler: customErrorHandler,
|
||||||
}),
|
BodyLimit: 50 * 1024 * 1024, // 50 MB
|
||||||
cfg: config.Get(),
|
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
|
||||||
@@ -73,9 +86,12 @@ func (s *Server) Setup() error {
|
|||||||
|
|
||||||
// API routes
|
// API routes
|
||||||
s.api = s.app.Group("/api/v1")
|
s.api = s.app.Group("/api/v1")
|
||||||
|
s.api.Use(middleware.Authenticate())
|
||||||
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.Authorize())
|
||||||
|
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)
|
||||||
@@ -97,10 +113,6 @@ func (s *Server) Setup() error {
|
|||||||
productTranslation := s.restricted.Group("/product-translation")
|
productTranslation := s.restricted.Group("/product-translation")
|
||||||
restricted.ProductTranslationHandlerRoutes(productTranslation)
|
restricted.ProductTranslationHandlerRoutes(productTranslation)
|
||||||
|
|
||||||
// lists of things routes (restricted)
|
|
||||||
list := s.restricted.Group("/list")
|
|
||||||
restricted.ListHandlerRoutes(list)
|
|
||||||
|
|
||||||
product := s.restricted.Group("/product")
|
product := s.restricted.Group("/product")
|
||||||
restricted.ProductsHandlerRoutes(product)
|
restricted.ProductsHandlerRoutes(product)
|
||||||
|
|
||||||
@@ -121,12 +133,29 @@ func (s *Server) Setup() error {
|
|||||||
carts := s.restricted.Group("/carts")
|
carts := s.restricted.Group("/carts")
|
||||||
restricted.CartsHandlerRoutes(carts)
|
restricted.CartsHandlerRoutes(carts)
|
||||||
|
|
||||||
|
// orders (restricted)
|
||||||
|
orders := s.restricted.Group("/orders")
|
||||||
|
restricted.OrdersHandlerRoutes(orders)
|
||||||
|
|
||||||
|
specificPrice := s.restricted.Group("/specific-price")
|
||||||
|
restricted.SpecificPriceHandlerRoutes(specificPrice)
|
||||||
|
|
||||||
|
// addresses (restricted)
|
||||||
|
addresses := s.restricted.Group("/addresses")
|
||||||
|
restricted.AddressesHandlerRoutes(addresses)
|
||||||
|
|
||||||
|
// storage (uses various authorization means)
|
||||||
|
restrictedStorage := s.restricted.Group("/storage")
|
||||||
|
webdavStorage := s.webdav.Group("/storage")
|
||||||
|
restricted.StorageHandlerRoutes(restrictedStorage)
|
||||||
|
webdav.StorageHandlerRoutes(webdavStorage)
|
||||||
|
|
||||||
|
restricted.CurrencyHandlerRoutes(s.restricted)
|
||||||
|
|
||||||
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)
|
||||||
})
|
})
|
||||||
|
|
||||||
restricted.CurrencyHandlerRoutes(s.restricted)
|
|
||||||
|
|
||||||
// // Restricted routes example
|
// // Restricted routes example
|
||||||
// restricted := s.api.Group("/restricted")
|
// restricted := s.api.Group("/restricted")
|
||||||
// restricted.Use(middleware.AuthMiddleware())
|
// restricted.Use(middleware.AuthMiddleware())
|
||||||
@@ -138,16 +167,6 @@ func (s *Server) Setup() error {
|
|||||||
// })
|
// })
|
||||||
// })
|
// })
|
||||||
|
|
||||||
// // Admin routes example
|
|
||||||
// admin := s.api.Group("/admin")
|
|
||||||
// admin.Use(middleware.AuthMiddleware())
|
|
||||||
// admin.Use(middleware.RequireAdmin())
|
|
||||||
// admin.Get("/users", func(c fiber.Ctx) error {
|
|
||||||
// return c.JSON(fiber.Map{
|
|
||||||
// "message": "Admin area - user management",
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
|
|
||||||
// keep this at the end because its wilderange
|
// keep this at the end because its wilderange
|
||||||
general.InitBo(s.App())
|
general.InitBo(s.App())
|
||||||
|
|
||||||
|
|||||||
72
app/model/address.go
Normal file
72
app/model/address.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
||||||
|
CustomerID uint `gorm:"column:b2b_customer_id;not null;index" json:"customer_id"`
|
||||||
|
AddressString string `gorm:"column:address_string;not null" json:"address_string"`
|
||||||
|
AddressUnparsed *AddressUnparsed `gorm:"-" json:"address_unparsed"`
|
||||||
|
CountryID uint `gorm:"column:b2b_country_id;not null" json:"country_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Address) TableName() string {
|
||||||
|
return "b2b_addresses"
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddressUnparsed interface{}
|
||||||
|
|
||||||
|
// Address template in Poland
|
||||||
|
type AddressPL struct {
|
||||||
|
PostalCode string `json:"postal_code"` // format: 00-000
|
||||||
|
City string `json:"city"` // e.g. Kraków
|
||||||
|
Voivodeship string `json:"voivodeship"` // e.g. małopolskie (optional but useful)
|
||||||
|
|
||||||
|
Street string `json:"street"` // e.g. Marszałkowska
|
||||||
|
BuildingNo string `json:"building_no"` // e.g. 10, 221B, 12A
|
||||||
|
ApartmentNo string `json:"apartment_no"` // e.g. 5, 12B
|
||||||
|
|
||||||
|
AddressLine2 string `json:"address_line2"` // optional extra info
|
||||||
|
|
||||||
|
Recipient string `json:"recipient"` // name/company
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address template in Great Britain
|
||||||
|
type AddressGB struct {
|
||||||
|
PostalCode string `json:"postal_code"` // e.g. SW1A 1AA
|
||||||
|
PostTown string `json:"post_town"` // e.g. London
|
||||||
|
County string `json:"county"` // optional
|
||||||
|
|
||||||
|
Thoroughfare string `json:"thoroughfare"` // street name, e.g. Baker Street
|
||||||
|
BuildingNo string `json:"building_no"` // e.g. 221B
|
||||||
|
BuildingName string `json:"building_name"` // e.g. Flatiron House
|
||||||
|
SubBuilding string `json:"sub_building"` // e.g. Flat 5, Apt 2
|
||||||
|
|
||||||
|
AddressLine2 string `json:"address_line2"`
|
||||||
|
Recipient string `json:"recipient"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address template in Czech Republic
|
||||||
|
type AddressCZ struct {
|
||||||
|
PostalCode string `json:"postal_code"` // usually 110 00 or 11000
|
||||||
|
City string `json:"city"` // e.g. Praha
|
||||||
|
Region string `json:"region"`
|
||||||
|
|
||||||
|
Street string `json:"street"` // may be omitted in some village-style addresses
|
||||||
|
HouseNumber string `json:"house_number"` // descriptive / conscription no.
|
||||||
|
OrientationNumber string `json:"orientation_number"` // optional, often after slash
|
||||||
|
|
||||||
|
AddressLine2 string `json:"address_line2"`
|
||||||
|
Recipient string `json:"recipient"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address template in Germany
|
||||||
|
type AddressDE struct {
|
||||||
|
PostalCode string `json:"postal_code"` // e.g. 10115
|
||||||
|
City string `json:"city"` // e.g. Berlin
|
||||||
|
State string `json:"state"` // Bundesland, optional
|
||||||
|
|
||||||
|
Street string `json:"street"` // e.g. Unter den Linden
|
||||||
|
HouseNumber string `json:"house_number"` // e.g. 77, 12a
|
||||||
|
|
||||||
|
AddressLine2 string `json:"address_line2"` // extra details
|
||||||
|
Recipient string `json:"recipient"`
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ type ScannedCategory struct {
|
|||||||
LinkRewrite string `gorm:"column:link_rewrite"`
|
LinkRewrite string `gorm:"column:link_rewrite"`
|
||||||
IsoCode string `gorm:"column:iso_code"`
|
IsoCode string `gorm:"column:iso_code"`
|
||||||
|
|
||||||
Visited bool //this is for internal backend use only
|
Visited bool // this is for internal backend use only
|
||||||
|
Filter string // filter applicable to this category
|
||||||
}
|
}
|
||||||
|
|
||||||
type Category struct {
|
type Category struct {
|
||||||
@@ -25,6 +26,7 @@ type CategoryParams struct {
|
|||||||
CategoryID uint `json:"category_id" form:"category_id"`
|
CategoryID uint `json:"category_id" form:"category_id"`
|
||||||
LinkRewrite string `json:"link_rewrite" form:"link_rewrite"`
|
LinkRewrite string `json:"link_rewrite" form:"link_rewrite"`
|
||||||
Locale string `json:"locale" form:"locale"`
|
Locale string `json:"locale" form:"locale"`
|
||||||
|
Filter string `json:"filter" form:"filter"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CategoryInBreadcrumb struct {
|
type CategoryInBreadcrumb struct {
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import "git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
|
||||||
|
|
||||||
// Represents a country together with its associated currency
|
// Represents a country together with its associated currency
|
||||||
type Country struct {
|
type Country struct {
|
||||||
ID uint `gorm:"primaryKey;column:id" json:"id"`
|
ID uint `gorm:"primaryKey;column:id" json:"id"`
|
||||||
Name string `gorm:"column:name" json:"name"`
|
Name string `gorm:"column:name" json:"name"`
|
||||||
Flag string `gorm:"size:16;not null;column:flag" json:"flag"`
|
Flag string `gorm:"size:16;not null;column:flag" json:"flag"`
|
||||||
|
|
||||||
PSCurrencyID uint `gorm:"column:currency_id" json:"currency_id"`
|
CurrencyID uint `gorm:"column:b2b_id_currency" json:"currency_id"`
|
||||||
PSCurrency *dbmodel.PsCurrency `gorm:"foreignKey:PSCurrencyID;references:IDCurrency" json:"ps_currency"`
|
Currency *Currency `gorm:"foreignKey:CurrencyID" json:"currency,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Country) TableName() string {
|
func (Country) TableName() string {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type Customer struct {
|
|||||||
FirstName string `gorm:"size:100" json:"first_name"`
|
FirstName string `gorm:"size:100" json:"first_name"`
|
||||||
LastName string `gorm:"size:100" json:"last_name"`
|
LastName string `gorm:"size:100" json:"last_name"`
|
||||||
RoleID uint `gorm:"column:role_id;not null;default:1" json:"-"`
|
RoleID uint `gorm:"column:role_id;not null;default:1" json:"-"`
|
||||||
Role Role `gorm:"foreignKey:RoleID" json:"role"`
|
Role *Role `gorm:"foreignKey:RoleID" json:"role,omitempty"`
|
||||||
Provider AuthProvider `gorm:"type:varchar(20);default:'local'" json:"provider"`
|
Provider AuthProvider `gorm:"type:varchar(20);default:'local'" json:"provider"`
|
||||||
ProviderID string `gorm:"size:255" json:"provider_id,omitempty"` // ID from OAuth provider
|
ProviderID string `gorm:"size:255" json:"provider_id,omitempty"` // ID from OAuth provider
|
||||||
AvatarURL string `gorm:"size:500" json:"avatar_url,omitempty"`
|
AvatarURL string `gorm:"size:500" json:"avatar_url,omitempty"`
|
||||||
@@ -25,15 +25,27 @@ 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
|
||||||
CountryID uint `gorm:"default:2" json:"country_id"` // User's selected country
|
CountryID uint `gorm:"default:2" json:"country_id"` // User's selected country
|
||||||
|
Country *Country `gorm:"foreignKey:CountryID" json:"country,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *Customer) HasPermission(permission perms.Permission) bool {
|
||||||
|
for _, p := range u.Role.Permissions {
|
||||||
|
if p.Name == permission {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// AuthProvider represents the authentication provider
|
// AuthProvider represents the authentication provider
|
||||||
type AuthProvider string
|
type AuthProvider string
|
||||||
|
|
||||||
@@ -77,6 +89,15 @@ func (us *UserSession) HasPermission(permission perms.Permission) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
@@ -106,6 +127,7 @@ func BuildPermissionSlice(user *Customer) []perms.Permission {
|
|||||||
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
|
||||||
@@ -158,5 +180,4 @@ type UserInList struct {
|
|||||||
Email string `gorm:"column:email" json:"email"`
|
Email string `gorm:"column:email" json:"email"`
|
||||||
FirstName string `gorm:"column:first_name" json:"first_name"`
|
FirstName string `gorm:"column:first_name" json:"first_name"`
|
||||||
LastName string `gorm:"column:last_name" json:"last_name"`
|
LastName string `gorm:"column:last_name" json:"last_name"`
|
||||||
Role string `gorm:"column:role" json:"role"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
27
app/model/order.go
Normal file
27
app/model/order.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type CustomerOrder struct {
|
||||||
|
OrderID uint `gorm:"column:order_id;primaryKey;autoIncrement" json:"order_id"`
|
||||||
|
UserID uint `gorm:"column:user_id;not null;index" json:"user_id"`
|
||||||
|
Name string `gorm:"column:name;not null" json:"name"`
|
||||||
|
CountryID uint `gorm:"column:country_id;not null" json:"country_id"`
|
||||||
|
AddressString string `gorm:"column:address_string;not null" json:"address_string"`
|
||||||
|
AddressUnparsed *AddressUnparsed `gorm:"-" json:"address_unparsed"`
|
||||||
|
Status string `gorm:"column:status;size:50;not null" json:"status"`
|
||||||
|
Products []OrderProduct `gorm:"foreignKey:OrderID;references:OrderID" json:"products"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (CustomerOrder) TableName() string {
|
||||||
|
return "b2b_customer_orders"
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderProduct struct {
|
||||||
|
OrderID uint `gorm:"column:order_id;not null;index" json:"-"`
|
||||||
|
ProductID uint `gorm:"column:product_id;not null" json:"product_id"`
|
||||||
|
ProductAttributeID *uint `gorm:"column:product_attribute_id" json:"product_attribute_id,omitempty"`
|
||||||
|
Amount uint `gorm:"column:amount;not null" json:"amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OrderProduct) TableName() string {
|
||||||
|
return "b2b_orders_products"
|
||||||
|
}
|
||||||
@@ -1,66 +1,5 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
// Product contains each and every column from the table ps_product.
|
|
||||||
type Product struct {
|
|
||||||
ProductID uint `gorm:"column:id_product;primaryKey" json:"product_id" form:"product_id"`
|
|
||||||
SupplierID uint `gorm:"column:id_supplier" json:"supplier_id" form:"supplier_id"`
|
|
||||||
ManufacturerID uint `gorm:"column:id_manufacturer" json:"manufacturer_id" form:"manufacturer_id"`
|
|
||||||
CategoryDefaultID uint `gorm:"column:id_category_default" json:"category_default_id" form:"category_default_id"`
|
|
||||||
ShopDefaultID uint `gorm:"column:id_shop_default" json:"shop_default_id" form:"shop_default_id"`
|
|
||||||
TaxRulesGroupID uint `gorm:"column:id_tax_rules_group" json:"tax_rules_group_id" form:"tax_rules_group_id"`
|
|
||||||
OnSale uint `gorm:"column:on_sale" json:"on_sale" form:"on_sale"`
|
|
||||||
OnlineOnly uint `gorm:"column:online_only" json:"online_only" form:"online_only"`
|
|
||||||
EAN13 string `gorm:"column:ean13;type:varchar(13)" json:"ean13" form:"ean13"`
|
|
||||||
ISBN string `gorm:"column:isbn;type:varchar(32)" json:"isbn" form:"isbn"`
|
|
||||||
UPC string `gorm:"column:upc;type:varchar(12)" json:"upc" form:"upc"`
|
|
||||||
EkoTax float32 `gorm:"column:eko_tax;type:decimal(20,6)" json:"eko_tax" form:"eko_tax"`
|
|
||||||
Quantity uint `gorm:"column:quantity" json:"quantity" form:"quantity"`
|
|
||||||
MinimalQuantity uint `gorm:"column:minimal_quantity" json:"minimal_quantity" form:"minimal_quantity"`
|
|
||||||
LowStockThreshold uint `gorm:"column:low_stock_threshold" json:"low_stock_threshold" form:"low_stock_threshold"`
|
|
||||||
LowStockAlert uint `gorm:"column:low_stock_alert" json:"low_stock_alert" form:"low_stock_alert"`
|
|
||||||
Price float32 `gorm:"column:price;type:decimal(20,6)" json:"price" form:"price"`
|
|
||||||
WholesalePrice float32 `gorm:"column:wholesale_price;type:decimal(20,6)" json:"wholesale_price" form:"wholesale_price"`
|
|
||||||
Unity string `gorm:"column:unity;type:varchar(255)" json:"unity" form:"unity"`
|
|
||||||
UnitPriceRatio float32 `gorm:"column:unit_price_ratio;type:decimal(20,6)" json:"unit_price_ratio" form:"unit_price_ratio"`
|
|
||||||
UnitID uint `gorm:"column:id_unit;primaryKey" json:"unit_id" form:"unit_id"`
|
|
||||||
AdditionalShippingCost float32 `gorm:"column:additional_shipping_cost;type:decimal(20,2)" json:"additional_shipping_cost" form:"additional_shipping_cost"`
|
|
||||||
Reference string `gorm:"column:reference;type:varchar(64)" json:"reference" form:"reference"`
|
|
||||||
SupplierReference string `gorm:"column:supplier_reference;type:varchar(64)" json:"supplier_reference" form:"supplier_reference"`
|
|
||||||
Location string `gorm:"column:location;type:varchar(64)" json:"location" form:"location"`
|
|
||||||
|
|
||||||
Width float32 `gorm:"column:width;type:decimal(20,6)" json:"width" form:"width"`
|
|
||||||
Height float32 `gorm:"column:height;type:decimal(20,6)" json:"height" form:"height"`
|
|
||||||
Depth float32 `gorm:"column:depth;type:decimal(20,6)" json:"depth" form:"depth"`
|
|
||||||
Weight float32 `gorm:"column:weight;type:decimal(20,6)" json:"weight" form:"weight"`
|
|
||||||
OutOfStock uint `gorm:"column:out_of_stock" json:"out_of_stock" form:"out_of_stock"`
|
|
||||||
AdditionalDeliveryTimes uint `gorm:"column:additional_delivery_times" json:"additional_delivery_times" form:"additional_delivery_times"`
|
|
||||||
QuantityDiscount uint `gorm:"column:quantity_discount" json:"quantity_discount" form:"quantity_discount"`
|
|
||||||
Customizable uint `gorm:"column:customizable" json:"customizable" form:"customizable"`
|
|
||||||
UploadableFiles uint `gorm:"column:uploadable_files" json:"uploadable_files" form:"uploadable_files"`
|
|
||||||
TextFields uint `gorm:"column:text_fields" json:"text_fields" form:"text_fields"`
|
|
||||||
|
|
||||||
Active uint `gorm:"column:active" json:"active" form:"active"`
|
|
||||||
RedirectType string `gorm:"column:redirect_type;type:enum('','404','301-product','302-product','301-category','302-category')" json:"redirect_type" form:"redirect_type"`
|
|
||||||
TypeRedirectedID int `gorm:"column:id_type_redirected" json:"type_redirected_id" form:"type_redirected_id"`
|
|
||||||
AvailableForOrder uint `gorm:"column:available_for_order" json:"available_for_order" form:"available_for_order"`
|
|
||||||
AvailableDate string `gorm:"column:available_date;type:date" json:"available_date" form:"available_date"`
|
|
||||||
ShowCondition uint `gorm:"column:show_condition" json:"show_condition" form:"show_condition"`
|
|
||||||
Condition string `gorm:"column:condition;type:enum('new','used','refurbished')" json:"condition" form:"condition"`
|
|
||||||
ShowPrice uint `gorm:"column:show_price" json:"show_price" form:"show_price"`
|
|
||||||
|
|
||||||
Indexed uint `gorm:"column:indexed" json:"indexed" form:"indexed"`
|
|
||||||
Visibility string `gorm:"column:visibility;type:enum('both','catalog','search','none')" json:"visibility" form:"visibility"`
|
|
||||||
CacheIsPack uint `gorm:"column:cache_is_pack" json:"cache_is_pack" form:"cache_is_pack"`
|
|
||||||
CacheHasAttachments uint `gorm:"column:cache_has_attachments" json:"cache_has_attachments" form:"cache_has_attachments"`
|
|
||||||
IsVirtual uint `gorm:"column:is_virtual" json:"is_virtual" form:"is_virtual"`
|
|
||||||
CacheDefaultAttribute uint `gorm:"column:cache_default_attribute" json:"cache_default_attribute" form:"cache_default_attribute"`
|
|
||||||
DateAdd string `gorm:"column:date_add;type:datetime" json:"date_add" form:"date_add"`
|
|
||||||
DateUpd string `gorm:"column:date_upd;type:datetime" json:"date_upd" form:"date_upd"`
|
|
||||||
AdvancedStockManagement uint `gorm:"column:advanced_stock_management" json:"advanced_stock_management" form:"advanced_stock_management"`
|
|
||||||
PackStockType uint `gorm:"column:pack_stock_type" json:"pack_stock_type" form:"pack_stock_type"`
|
|
||||||
State uint `gorm:"column:state" json:"state" form:"state"`
|
|
||||||
DeliveryDays uint `gorm:"column:delivery_days" json:"delivery_days" form:"delivery_days"`
|
|
||||||
}
|
|
||||||
type ProductInList struct {
|
type ProductInList struct {
|
||||||
ProductID uint `gorm:"column:product_id" json:"product_id" form:"product_id"`
|
ProductID uint `gorm:"column:product_id" json:"product_id" form:"product_id"`
|
||||||
Name string `gorm:"column:name" json:"name" form:"name"`
|
Name string `gorm:"column:name" json:"name" form:"name"`
|
||||||
@@ -70,6 +9,10 @@ type ProductInList struct {
|
|||||||
Reference string `gorm:"column:reference" json:"reference"`
|
Reference string `gorm:"column:reference" json:"reference"`
|
||||||
VariantsNumber uint `gorm:"column:variants_number" json:"variants_number"`
|
VariantsNumber uint `gorm:"column:variants_number" json:"variants_number"`
|
||||||
Quantity int64 `gorm:"column:quantity" json:"quantity"`
|
Quantity int64 `gorm:"column:quantity" json:"quantity"`
|
||||||
|
PriceTaxExcl float64 `gorm:"column:price_tax_excl" json:"price_tax_excl"`
|
||||||
|
PriceTaxIncl float64 `gorm:"column:price_tax_incl" json:"price_tax_incl"`
|
||||||
|
IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"`
|
||||||
|
IsNew uint `gorm:"column:is_new" json:"is_new"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductFilters struct {
|
type ProductFilters struct {
|
||||||
@@ -85,3 +28,12 @@ type ProductFilters struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FeatVal = map[uint][]uint
|
type FeatVal = map[uint][]uint
|
||||||
|
|
||||||
|
type B2bFavorite struct {
|
||||||
|
UserID uint `gorm:"column:user_id;not null;primaryKey" json:"user_id"`
|
||||||
|
ProductID uint `gorm:"column:product_id;not null;primaryKey" json:"product_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*B2bFavorite) TableName() string {
|
||||||
|
return "b2b_favorites"
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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"`
|
||||||
|
|
||||||
ImageLink string `gorm:"column:image_link" json:"image_link"`
|
ImageLink string `gorm:"column:image_link" json:"image_link"`
|
||||||
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
|
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package model
|
|||||||
type Role struct {
|
type Role struct {
|
||||||
ID uint `gorm:"primaryKey" json:"id"`
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
Name string `gorm:"size:64" json:"name"`
|
Name string `gorm:"size:64" json:"name"`
|
||||||
Permissions []Permission `gorm:"many2many:b2b_role_permissions;" json:"-"`
|
Permissions []Permission `gorm:"many2many:b2b_role_permissions;" json:"permissions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Role) TableName() string {
|
func (Role) TableName() string {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ type Route struct {
|
|||||||
Component string `gorm:"type:varchar(255);not null;comment:path to component file" json:"component"`
|
Component string `gorm:"type:varchar(255);not null;comment:path to component file" json:"component"`
|
||||||
Meta *string `gorm:"type:longtext;default:'{}'" json:"meta,omitempty"`
|
Meta *string `gorm:"type:longtext;default:'{}'" json:"meta,omitempty"`
|
||||||
Active *bool `gorm:"type:tinyint;default:1" json:"active,omitempty"`
|
Active *bool `gorm:"type:tinyint;default:1" json:"active,omitempty"`
|
||||||
SortOrder *int `gorm:"type:int;default:0" json:"sort_order,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Route) TableName() string {
|
func (Route) TableName() string {
|
||||||
|
|||||||
29
app/model/specificPrice.go
Normal file
29
app/model/specificPrice.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type SpecificPrice struct {
|
||||||
|
ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
|
Name string `gorm:"type:varchar(255);not null" json:"name"`
|
||||||
|
ValidFrom *time.Time `gorm:"null" json:"valid_from"`
|
||||||
|
ValidTill *time.Time `gorm:"null" json:"valid_till"`
|
||||||
|
HasExpirationDate bool `gorm:"default:false" json:"has_expiration_date"`
|
||||||
|
ReductionType string `gorm:"type:enum('amount','percentage');not null" json:"reduction_type"`
|
||||||
|
Price *float64 `gorm:"type:decimal(10,2);null" json:"price"`
|
||||||
|
CurrencyID *uint64 `gorm:"column:b2b_id_currency;null" json:"currency_id"`
|
||||||
|
PercentageReduction *float64 `gorm:"type:decimal(5,2);null" json:"percentage_reduction"`
|
||||||
|
FromQuantity uint32 `gorm:"default:1" json:"from_quantity"`
|
||||||
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||||
|
CreatedAt *time.Time `gorm:"null" json:"created_at"`
|
||||||
|
UpdatedAt *time.Time `gorm:"null" json:"updated_at"`
|
||||||
|
|
||||||
|
ProductIDs []uint64 `gorm:"-" json:"product_ids"`
|
||||||
|
CategoryIDs []uint64 `gorm:"-" json:"category_ids"`
|
||||||
|
ProductAttributeIDs []uint64 `gorm:"-" json:"product_attribute_ids"`
|
||||||
|
CountryIDs []uint64 `gorm:"-" json:"country_ids"`
|
||||||
|
CustomerIDs []uint64 `gorm:"-" json:"customer_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SpecificPrice) TableName() string {
|
||||||
|
return "b2b_specific_price"
|
||||||
|
}
|
||||||
91
app/repos/addressesRepo/addressesRepo.go
Normal file
91
app/repos/addressesRepo/addressesRepo.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package addressesRepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UIAddressesRepo interface {
|
||||||
|
UserHasAddress(user_id uint, address_id uint) (uint, error)
|
||||||
|
UserAddressesAmt(user_id uint) (uint, error)
|
||||||
|
AddNewAddress(user_id uint, address_info string, country_id uint) error
|
||||||
|
UpdateAddress(user_id uint, address_id uint, address_info string, country_id uint) error
|
||||||
|
RetrieveAddresses(user_id uint) (*[]model.Address, error)
|
||||||
|
DeleteAddress(user_id uint, address_id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddressesRepo struct{}
|
||||||
|
|
||||||
|
func New() UIAddressesRepo {
|
||||||
|
return &AddressesRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AddressesRepo) UserHasAddress(user_id uint, address_id uint) (uint, error) {
|
||||||
|
var amt uint
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Table("b2b_addresses").
|
||||||
|
Select("COUNT(*) AS amt").
|
||||||
|
Where("id = ? AND b2b_customer_id = ?", address_id, user_id).
|
||||||
|
Scan(&amt).
|
||||||
|
Error
|
||||||
|
|
||||||
|
return amt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AddressesRepo) UserAddressesAmt(user_id uint) (uint, error) {
|
||||||
|
var amt uint
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Table("b2b_addresses").
|
||||||
|
Select("COUNT(*) AS amt").
|
||||||
|
Where("b2b_customer_id = ?", user_id).
|
||||||
|
Scan(&amt).
|
||||||
|
Error
|
||||||
|
|
||||||
|
return amt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AddressesRepo) AddNewAddress(user_id uint, address_info string, country_id uint) error {
|
||||||
|
address := model.Address{
|
||||||
|
CustomerID: user_id,
|
||||||
|
AddressString: address_info,
|
||||||
|
CountryID: country_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.DB.
|
||||||
|
Create(&address).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AddressesRepo) UpdateAddress(user_id uint, address_id uint, address_info string, country_id uint) error {
|
||||||
|
address := model.Address{
|
||||||
|
ID: address_id,
|
||||||
|
CustomerID: user_id,
|
||||||
|
AddressString: address_info,
|
||||||
|
CountryID: country_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.DB.
|
||||||
|
Where("id = ? AND b2b_customer_id = ?", address_id, user_id).
|
||||||
|
Updates(&address).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AddressesRepo) RetrieveAddresses(user_id uint) (*[]model.Address, error) {
|
||||||
|
var addresses []model.Address
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Where("b2b_customer_id = ?", user_id).
|
||||||
|
Find(&addresses).
|
||||||
|
Error
|
||||||
|
|
||||||
|
return &addresses, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AddressesRepo) DeleteAddress(user_id uint, address_id uint) error {
|
||||||
|
return db.DB.
|
||||||
|
Where("id = ? AND b2b_customer_id = ?", address_id, user_id).
|
||||||
|
Delete(&model.Address{}).
|
||||||
|
Error
|
||||||
|
}
|
||||||
@@ -1,20 +1,26 @@
|
|||||||
package cartsRepo
|
package cartsRepo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"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"
|
||||||
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/responseErrors"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UICartsRepo interface {
|
type UICartsRepo interface {
|
||||||
CartsAmount(user_id uint) (uint, error)
|
CartsAmount(user_id uint) (uint, error)
|
||||||
CreateNewCart(user_id uint) (model.CustomerCart, error)
|
CreateNewCart(user_id uint) (model.CustomerCart, error)
|
||||||
UserHasCart(user_id uint, cart_id uint) (uint, error)
|
RemoveCart(user_id uint, cart_id uint) error
|
||||||
|
UserHasCart(user_id uint, cart_id uint) (bool, error)
|
||||||
UpdateCartName(user_id uint, cart_id uint, new_name string) error
|
UpdateCartName(user_id uint, cart_id uint, new_name string) error
|
||||||
RetrieveCartsInfo(user_id uint) ([]model.CustomerCart, error)
|
RetrieveCartsInfo(user_id uint) ([]model.CustomerCart, error)
|
||||||
RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error)
|
RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error)
|
||||||
CheckProductExists(product_id uint, product_attribute_id *uint) (uint, error)
|
CheckProductExists(product_id uint, product_attribute_id *uint) (bool, error)
|
||||||
AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error
|
AddProduct(cart_id uint, product_id uint, product_attribute_id *uint, amount uint, set_amount bool) error
|
||||||
|
RemoveProduct(cart_id uint, product_id uint, product_attribute_id *uint) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type CartsRepo struct{}
|
type CartsRepo struct{}
|
||||||
@@ -49,7 +55,15 @@ func (repo *CartsRepo) CreateNewCart(user_id uint) (model.CustomerCart, error) {
|
|||||||
return cart, err
|
return cart, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *CartsRepo) UserHasCart(user_id uint, cart_id uint) (uint, error) {
|
func (repo *CartsRepo) RemoveCart(user_id uint, cart_id uint) error {
|
||||||
|
return db.DB.
|
||||||
|
Table("b2b_customer_carts").
|
||||||
|
Where("cart_id = ? AND user_id = ?", cart_id, user_id).
|
||||||
|
Delete(nil).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CartsRepo) UserHasCart(user_id uint, cart_id uint) (bool, error) {
|
||||||
var amt uint
|
var amt uint
|
||||||
|
|
||||||
err := db.DB.
|
err := db.DB.
|
||||||
@@ -59,7 +73,7 @@ func (repo *CartsRepo) UserHasCart(user_id uint, cart_id uint) (uint, error) {
|
|||||||
Scan(&amt).
|
Scan(&amt).
|
||||||
Error
|
Error
|
||||||
|
|
||||||
return amt, err
|
return amt >= 1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *CartsRepo) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
func (repo *CartsRepo) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
||||||
@@ -96,7 +110,7 @@ func (repo *CartsRepo) RetrieveCart(user_id uint, cart_id uint) (*model.Customer
|
|||||||
return &cart, err
|
return &cart, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id *uint) (uint, error) {
|
func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id *uint) (bool, error) {
|
||||||
var amt uint
|
var amt uint
|
||||||
|
|
||||||
if product_attribute_id == nil {
|
if product_attribute_id == nil {
|
||||||
@@ -106,7 +120,7 @@ func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id
|
|||||||
Where("id_product = ?", product_id).
|
Where("id_product = ?", product_id).
|
||||||
Scan(&amt).
|
Scan(&amt).
|
||||||
Error
|
Error
|
||||||
return amt, err
|
return amt >= 1, err
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
err := db.DB.
|
err := db.DB.
|
||||||
@@ -116,18 +130,65 @@ func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id
|
|||||||
Where("ps.id_product = ? AND pas.id_product_attribute = ?", product_id, *product_attribute_id).
|
Where("ps.id_product = ? AND pas.id_product_attribute = ?", product_id, *product_attribute_id).
|
||||||
Scan(&amt).
|
Scan(&amt).
|
||||||
Error
|
Error
|
||||||
return amt, err
|
return amt >= 1, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *CartsRepo) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error {
|
func (repo *CartsRepo) AddProduct(cart_id uint, product_id uint, product_attribute_id *uint, amount uint, set_amount bool) error {
|
||||||
product := model.CartProduct{
|
var product model.CartProduct
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Where(&model.CartProduct{
|
||||||
|
CartID: cart_id,
|
||||||
|
ProductID: product_id,
|
||||||
|
ProductAttributeID: product_attribute_id,
|
||||||
|
}).
|
||||||
|
First(&product).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
if amount < 1 {
|
||||||
|
return responseErrors.ErrAmountMustBePositive
|
||||||
|
} else if amount > constdata.MAX_AMOUNT_OF_PRODUCT_IN_CART {
|
||||||
|
return responseErrors.ErrAmountMustBeReasonable
|
||||||
|
}
|
||||||
|
|
||||||
|
product = model.CartProduct{
|
||||||
CartID: cart_id,
|
CartID: cart_id,
|
||||||
ProductID: product_id,
|
ProductID: product_id,
|
||||||
ProductAttributeID: product_attribute_id,
|
ProductAttributeID: product_attribute_id,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
}
|
}
|
||||||
err := db.DB.Create(&product).Error
|
|
||||||
|
|
||||||
|
return db.DB.Create(&product).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some other DB error
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product already exists in cart
|
||||||
|
if set_amount {
|
||||||
|
product.Amount = amount
|
||||||
|
} else {
|
||||||
|
product.Amount = product.Amount + amount
|
||||||
|
}
|
||||||
|
|
||||||
|
if product.Amount < 1 {
|
||||||
|
return responseErrors.ErrAmountMustBePositive
|
||||||
|
} else if product.Amount > constdata.MAX_AMOUNT_OF_PRODUCT_IN_CART {
|
||||||
|
return responseErrors.ErrAmountMustBeReasonable
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.DB.Save(&product).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CartsRepo) RemoveProduct(cart_id uint, product_id uint, product_attribute_id *uint) error {
|
||||||
|
return db.DB.
|
||||||
|
Where(&model.CartProduct{
|
||||||
|
CartID: cart_id,
|
||||||
|
ProductID: product_id,
|
||||||
|
ProductAttributeID: product_attribute_id,
|
||||||
|
}).
|
||||||
|
Delete(&model.CartProduct{}).Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
package categoriesRepo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/db"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
|
||||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UICategoriesRepo interface {
|
|
||||||
GetAllCategories(idLang uint) ([]model.ScannedCategory, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CategoriesRepo struct{}
|
|
||||||
|
|
||||||
func New() UICategoriesRepo {
|
|
||||||
return &CategoriesRepo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *CategoriesRepo) GetAllCategories(idLang uint) ([]model.ScannedCategory, error) {
|
|
||||||
var allCategories []model.ScannedCategory
|
|
||||||
|
|
||||||
categoryTbl := (&dbmodel.PsCategory{}).TableName()
|
|
||||||
categoryLangTbl := (&dbmodel.PsCategoryLang{}).TableName()
|
|
||||||
categoryShopTbl := (&dbmodel.PsCategoryShop{}).TableName()
|
|
||||||
langTbl := (&dbmodel.PsLang{}).TableName()
|
|
||||||
|
|
||||||
err := db.Get().
|
|
||||||
Model(dbmodel.PsCategory{}).
|
|
||||||
Select(`
|
|
||||||
ps_category.id_category AS category_id,
|
|
||||||
ps_category_lang.name AS name,
|
|
||||||
ps_category.active AS active,
|
|
||||||
ps_category_shop.position AS position,
|
|
||||||
ps_category.id_parent AS id_parent,
|
|
||||||
ps_category.is_root_category AS is_root_category,
|
|
||||||
ps_category_lang.link_rewrite AS link_rewrite,
|
|
||||||
ps_lang.iso_code AS iso_code
|
|
||||||
`).
|
|
||||||
Joins(`LEFT JOIN `+categoryLangTbl+` ON `+categoryLangTbl+`.id_category = `+categoryTbl+`.id_category AND `+categoryLangTbl+`.id_shop = ? AND `+categoryLangTbl+`.id_lang = ?`,
|
|
||||||
constdata.SHOP_ID, idLang).
|
|
||||||
Joins(`LEFT JOIN `+categoryShopTbl+` ON `+categoryShopTbl+`.id_category = `+categoryTbl+`.id_category AND `+categoryShopTbl+`.id_shop = ?`,
|
|
||||||
constdata.SHOP_ID).
|
|
||||||
Joins(`JOIN ` + langTbl + ` ON ` + langTbl + `.id_lang = ` + categoryLangTbl + `.id_lang`).
|
|
||||||
Scan(&allCategories).Error
|
|
||||||
|
|
||||||
return allCategories, err
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,14 @@ package categoryrepo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"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/dbmodel"
|
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UICategoryRepo interface {
|
type UICategoryRepo interface {
|
||||||
GetCategoryTranslations(ids []uint, idLang uint) (map[uint]string, error)
|
GetCategoryTranslations(ids []uint, idLang uint) (map[uint]string, error)
|
||||||
|
RetrieveMenuCategories(idLang uint) ([]model.ScannedCategory, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CategoryRepo struct{}
|
type CategoryRepo struct{}
|
||||||
@@ -42,3 +45,33 @@ func (r *CategoryRepo) GetCategoryTranslations(ids []uint, idLang uint) (map[uin
|
|||||||
|
|
||||||
return translations, nil
|
return translations, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *CategoryRepo) RetrieveMenuCategories(idLang uint) ([]model.ScannedCategory, error) {
|
||||||
|
var allCategories []model.ScannedCategory
|
||||||
|
|
||||||
|
categoryTbl := (&dbmodel.PsCategory{}).TableName()
|
||||||
|
categoryLangTbl := (&dbmodel.PsCategoryLang{}).TableName()
|
||||||
|
categoryShopTbl := (&dbmodel.PsCategoryShop{}).TableName()
|
||||||
|
langTbl := (&dbmodel.PsLang{}).TableName()
|
||||||
|
|
||||||
|
err := db.Get().
|
||||||
|
Model(dbmodel.PsCategory{}).
|
||||||
|
Select(`
|
||||||
|
ps_category.id_category AS category_id,
|
||||||
|
ps_category_lang.name AS name,
|
||||||
|
ps_category.active AS active,
|
||||||
|
ps_category_shop.position AS position,
|
||||||
|
ps_category.id_parent AS id_parent,
|
||||||
|
ps_category.is_root_category AS is_root_category,
|
||||||
|
ps_category_lang.link_rewrite AS link_rewrite,
|
||||||
|
ps_lang.iso_code AS iso_code
|
||||||
|
`).
|
||||||
|
Joins(`LEFT JOIN `+categoryLangTbl+` ON `+categoryLangTbl+`.id_category = `+categoryTbl+`.id_category AND `+categoryLangTbl+`.id_shop = ? AND `+categoryLangTbl+`.id_lang = ?`,
|
||||||
|
constdata.SHOP_ID, idLang).
|
||||||
|
Joins(`LEFT JOIN `+categoryShopTbl+` ON `+categoryShopTbl+`.id_category = `+categoryTbl+`.id_category AND `+categoryShopTbl+`.id_shop = ?`,
|
||||||
|
constdata.SHOP_ID).
|
||||||
|
Joins(`JOIN ` + langTbl + ` ON ` + langTbl + `.id_lang = ` + categoryLangTbl + `.id_lang`).
|
||||||
|
Scan(&allCategories).Error
|
||||||
|
|
||||||
|
return allCategories, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
package customerRepo
|
package customerRepo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"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/utils/query/filters"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UICustomerRepo interface {
|
type UICustomerRepo interface {
|
||||||
Get(id uint) (*model.Customer, error)
|
Get(id uint) (*model.Customer, error)
|
||||||
|
GetByEmail(email string) (*model.Customer, error)
|
||||||
|
GetByExternalProviderId(provider model.AuthProvider, id string) (*model.Customer, error)
|
||||||
|
Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error)
|
||||||
|
Save(customer *model.Customer) error
|
||||||
|
Create(customer *model.Customer) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomerRepo struct{}
|
type CustomerRepo struct{}
|
||||||
@@ -19,9 +29,88 @@ func (repo *CustomerRepo) Get(id uint) (*model.Customer, error) {
|
|||||||
var customer model.Customer
|
var customer model.Customer
|
||||||
|
|
||||||
err := db.DB.
|
err := db.DB.
|
||||||
Preload("Role").
|
Preload("Role.Permissions").
|
||||||
First(&customer, id).
|
First(&customer, id).
|
||||||
Error
|
Error
|
||||||
|
|
||||||
return &customer, err
|
return &customer, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *CustomerRepo) GetByEmail(email string) (*model.Customer, error) {
|
||||||
|
var customer model.Customer
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Preload("Role.Permissions").
|
||||||
|
Where("email = ?", email).
|
||||||
|
First(&customer).
|
||||||
|
Error
|
||||||
|
|
||||||
|
return &customer, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CustomerRepo) GetByExternalProviderId(provider model.AuthProvider, id string) (*model.Customer, error) {
|
||||||
|
var customer model.Customer
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Preload("Role.Permissions").
|
||||||
|
Where("provider = ? AND provider_id = ?", provider, id).
|
||||||
|
First(&customer).
|
||||||
|
Error
|
||||||
|
|
||||||
|
return &customer, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CustomerRepo) Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error) {
|
||||||
|
|
||||||
|
query := db.DB.
|
||||||
|
Table("b2b_customers AS users").
|
||||||
|
Select(`
|
||||||
|
users.id AS id,
|
||||||
|
users.email AS email,
|
||||||
|
users.first_name AS first_name,
|
||||||
|
users.last_name AS last_name
|
||||||
|
`)
|
||||||
|
|
||||||
|
if search != "" {
|
||||||
|
words := strings.Fields(search)
|
||||||
|
if len(words) > 5 {
|
||||||
|
words = words[:5]
|
||||||
|
}
|
||||||
|
var conditions []string
|
||||||
|
var args []interface{}
|
||||||
|
for _, word := range words {
|
||||||
|
|
||||||
|
conditions = append(conditions, `
|
||||||
|
(
|
||||||
|
id = ? OR
|
||||||
|
LOWER(first_name) LIKE ? OR
|
||||||
|
LOWER(last_name) LIKE ? OR
|
||||||
|
LOWER(email) LIKE ?)
|
||||||
|
`)
|
||||||
|
|
||||||
|
args = append(args, strings.ToLower(word))
|
||||||
|
for range 3 {
|
||||||
|
args = append(args, fmt.Sprintf("%%%s%%", strings.ToLower(word)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conditionsQuery := strings.Join(conditions, " AND ")
|
||||||
|
|
||||||
|
query = query.Where(conditionsQuery, args...)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
query = query.Scopes(filt.All()...)
|
||||||
|
|
||||||
|
found, err := find.Paginate[model.UserInList](langId, p, query)
|
||||||
|
|
||||||
|
return &found, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CustomerRepo) Save(customer *model.Customer) error {
|
||||||
|
return db.DB.Save(customer).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CustomerRepo) Create(customer *model.Customer) error {
|
||||||
|
return db.DB.Create(customer).Error
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
package listRepo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/db"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
|
||||||
"git.ma-al.com/goc_marek/gormcol"
|
|
||||||
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UIListRepo interface {
|
|
||||||
ListProducts(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error)
|
|
||||||
ListUsers(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.UserInList], error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListRepo struct{}
|
|
||||||
|
|
||||||
func New() UIListRepo {
|
|
||||||
return &ListRepo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (repo *ListRepo) ListProducts(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error) {
|
|
||||||
var list []model.ProductInList
|
|
||||||
var total int64
|
|
||||||
|
|
||||||
query := db.Get().
|
|
||||||
Table(gormcol.Field.Tab(dbmodel.PsProductShopCols.Active)+" AS ps").
|
|
||||||
Select(`
|
|
||||||
ps.id_product AS product_id,
|
|
||||||
pl.name AS name,
|
|
||||||
pl.link_rewrite AS link_rewrite,
|
|
||||||
CONCAT(?, '/', ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
|
|
||||||
cl.name AS category_name,
|
|
||||||
p.reference AS reference,
|
|
||||||
COALESCE(v.variants_number, 0) AS variants_number,
|
|
||||||
sa.quantity AS quantity
|
|
||||||
`, config.Get().Image.ImagePrefix).
|
|
||||||
Joins("JOIN "+dbmodel.PsProductCols.IDProduct.Tab()+" p ON p.id_product = ps.id_product").
|
|
||||||
Joins("JOIN ps_product_lang pl ON pl.id_product = ps.id_product AND pl.id_lang = ?", id_lang).
|
|
||||||
Joins("JOIN ps_image_shop ims ON ims.id_product = ps.id_product AND ims.cover = 1").
|
|
||||||
Joins("JOIN ps_category_lang cl ON cl.id_category = ps.id_category_default AND cl.id_lang = ?", id_lang).
|
|
||||||
Joins("JOIN ps_category_product cp ON cp.id_product = ps.id_product").
|
|
||||||
Joins("LEFT JOIN variants v ON v.id_product = ps.id_product").
|
|
||||||
Joins("LEFT JOIN ps_stock_available sa ON sa.id_product = ps.id_product AND sa.id_product_attribute = 0").
|
|
||||||
Where("ps.active = ?", 1).
|
|
||||||
Group("ps.id_product").
|
|
||||||
Clauses(exclause.With{CTEs: []exclause.CTE{
|
|
||||||
{
|
|
||||||
Name: "variants",
|
|
||||||
Subquery: exclause.Subquery{DB: db.Get().Model(&dbmodel.PsProductAttributeShop{}).Select("id_product", "COUNT(*) AS variants_number").Group("id_product")},
|
|
||||||
},
|
|
||||||
}})
|
|
||||||
|
|
||||||
// Apply all filters
|
|
||||||
if filt != nil {
|
|
||||||
filt.ApplyAll(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
// run counter first as query is without limit and offset
|
|
||||||
err := query.Count(&total).Error
|
|
||||||
if err != nil {
|
|
||||||
return find.Found[model.ProductInList]{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = query.
|
|
||||||
Order("ps.id_product DESC").
|
|
||||||
Limit(p.Limit()).
|
|
||||||
Offset(p.Offset()).
|
|
||||||
Find(&list).Error
|
|
||||||
if err != nil {
|
|
||||||
return find.Found[model.ProductInList]{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return find.Found[model.ProductInList]{
|
|
||||||
Items: list,
|
|
||||||
Count: uint(total),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (repo *ListRepo) ListUsers(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.UserInList], error) {
|
|
||||||
var list []model.UserInList
|
|
||||||
var total int64
|
|
||||||
|
|
||||||
query := db.Get().
|
|
||||||
Table("b2b_customers AS users").
|
|
||||||
Select(`
|
|
||||||
users.id AS id,
|
|
||||||
users.email AS email,
|
|
||||||
users.first_name AS first_name,
|
|
||||||
users.last_name AS last_name,
|
|
||||||
users.role AS role
|
|
||||||
`)
|
|
||||||
|
|
||||||
// Apply all filters
|
|
||||||
if filt != nil {
|
|
||||||
filt.ApplyAll(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
// run counter first as query is without limit and offset
|
|
||||||
err := query.Count(&total).Error
|
|
||||||
if err != nil {
|
|
||||||
return find.Found[model.UserInList]{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = query.
|
|
||||||
Order("users.id DESC").
|
|
||||||
Limit(p.Limit()).
|
|
||||||
Offset(p.Offset()).
|
|
||||||
Find(&list).Error
|
|
||||||
if err != nil {
|
|
||||||
return find.Found[model.UserInList]{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return find.Found[model.UserInList]{
|
|
||||||
Items: list,
|
|
||||||
Count: uint(total),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
110
app/repos/ordersRepo/ordersRepo.go
Normal file
110
app/repos/ordersRepo/ordersRepo.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package ordersRepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UIOrdersRepo interface {
|
||||||
|
UserHasOrder(user_id uint, order_id uint) (bool, error)
|
||||||
|
Find(user_id uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.CustomerOrder], error)
|
||||||
|
PlaceNewOrder(cart *model.CustomerCart, name string, country_id uint, address_info string) error
|
||||||
|
ChangeOrderAddress(order_id uint, country_id uint, address_info string) error
|
||||||
|
ChangeOrderStatus(order_id uint, status string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrdersRepo struct{}
|
||||||
|
|
||||||
|
func New() UIOrdersRepo {
|
||||||
|
return &OrdersRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *OrdersRepo) UserHasOrder(user_id uint, order_id uint) (bool, error) {
|
||||||
|
var amt uint
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Table("b2b_customer_orders").
|
||||||
|
Select("COUNT(*) AS amt").
|
||||||
|
Where("user_id = ? AND order_id = ?", user_id, order_id).
|
||||||
|
Scan(&amt).
|
||||||
|
Error
|
||||||
|
|
||||||
|
return amt >= 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *OrdersRepo) Find(user_id uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.CustomerOrder], error) {
|
||||||
|
var list []model.CustomerOrder
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := db.Get().
|
||||||
|
Model(&model.CustomerOrder{}).
|
||||||
|
Preload("Products").
|
||||||
|
Order("b2b_customer_orders.order_id DESC")
|
||||||
|
|
||||||
|
// Apply all filters
|
||||||
|
if filt != nil {
|
||||||
|
filt.ApplyAll(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run counter first as query is without limit and offset
|
||||||
|
err := query.Count(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
return &find.Found[model.CustomerOrder]{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = query.
|
||||||
|
Limit(p.Limit()).
|
||||||
|
Offset(p.Offset()).
|
||||||
|
Find(&list).Error
|
||||||
|
if err != nil {
|
||||||
|
return &find.Found[model.CustomerOrder]{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &find.Found[model.CustomerOrder]{
|
||||||
|
Items: list,
|
||||||
|
Count: uint(total),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *OrdersRepo) PlaceNewOrder(cart *model.CustomerCart, name string, country_id uint, address_info string) error {
|
||||||
|
order := model.CustomerOrder{
|
||||||
|
UserID: cart.UserID,
|
||||||
|
Name: name,
|
||||||
|
CountryID: country_id,
|
||||||
|
AddressString: address_info,
|
||||||
|
Status: constdata.NEW_ORDER_STATUS,
|
||||||
|
Products: make([]model.OrderProduct, 0, len(cart.Products)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, product := range cart.Products {
|
||||||
|
order.Products = append(order.Products, model.OrderProduct{
|
||||||
|
ProductID: product.ProductID,
|
||||||
|
ProductAttributeID: product.ProductAttributeID,
|
||||||
|
Amount: product.Amount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.DB.Create(&order).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *OrdersRepo) ChangeOrderAddress(order_id uint, country_id uint, address_info string) error {
|
||||||
|
return db.DB.
|
||||||
|
Table("b2b_customer_orders").
|
||||||
|
Where("order_id = ?", order_id).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"country_id": country_id,
|
||||||
|
"address_string": address_info,
|
||||||
|
}).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *OrdersRepo) ChangeOrderStatus(order_id uint, status string) error {
|
||||||
|
return db.DB.
|
||||||
|
Table("b2b_customer_orders").
|
||||||
|
Where("order_id = ?", order_id).
|
||||||
|
Update("status", status).
|
||||||
|
Error
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ 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/model/dbmodel"
|
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
||||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +16,6 @@ type UIProductDescriptionRepo interface {
|
|||||||
GetProductDescription(productID uint, productid_lang uint) (*model.ProductDescription, error)
|
GetProductDescription(productID uint, productid_lang uint) (*model.ProductDescription, error)
|
||||||
CreateIfDoesNotExist(productID uint, productid_lang uint) error
|
CreateIfDoesNotExist(productID uint, productid_lang uint) error
|
||||||
UpdateFields(productID uint, productid_lang uint, updates map[string]string) error
|
UpdateFields(productID uint, productid_lang uint, updates map[string]string) error
|
||||||
GetMeiliProducts(id_lang uint, offset, limit int) ([]model.MeiliSearchProduct, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductDescriptionRepo struct{}
|
type ProductDescriptionRepo struct{}
|
||||||
@@ -52,7 +50,7 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid
|
|||||||
`+dbmodel.PsProductLangCols.AvailableLater.TabCol()+` AS available_later,
|
`+dbmodel.PsProductLangCols.AvailableLater.TabCol()+` AS available_later,
|
||||||
`+dbmodel.PsProductLangCols.DeliveryInStock.TabCol()+` AS delivery_in_stock,
|
`+dbmodel.PsProductLangCols.DeliveryInStock.TabCol()+` AS delivery_in_stock,
|
||||||
`+dbmodel.PsProductLangCols.DeliveryOutStock.TabCol()+` AS delivery_out_stock,
|
`+dbmodel.PsProductLangCols.DeliveryOutStock.TabCol()+` AS delivery_out_stock,
|
||||||
`+dbmodel.PsProductLangCols.Usage.TabCol()+` AS _usage_,
|
`+dbmodel.PsProductLangCols.Usage.TabCol()+` AS `+"`usage`"+`,
|
||||||
CONCAT(?, '/', `+dbmodel.PsImageShopCols.IDImage.TabCol()+`, '-large_default/', `+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+`, '.webp') AS image_link
|
CONCAT(?, '/', `+dbmodel.PsImageShopCols.IDImage.TabCol()+`, '-large_default/', `+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+`, '.webp') AS image_link
|
||||||
`, config.Get().Image.ImagePrefix).
|
`, config.Get().Image.ImagePrefix).
|
||||||
Joins("JOIN " + dbmodel.TableNamePsImageShop +
|
Joins("JOIN " + dbmodel.TableNamePsImageShop +
|
||||||
@@ -118,108 +116,3 @@ func (r *ProductDescriptionRepo) UpdateFields(productID uint, productid_lang uin
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMeiliProductsBatchedScanned returns a batch of products with LIMIT/OFFSET pagination
|
|
||||||
// The scanning is done inside the repo to keep the service layer cleaner
|
|
||||||
func (r *ProductDescriptionRepo) GetMeiliProducts(id_lang uint, offset, limit int) ([]model.MeiliSearchProduct, error) {
|
|
||||||
|
|
||||||
var products []model.MeiliSearchProduct
|
|
||||||
|
|
||||||
err := db.Get().
|
|
||||||
Table("ps_product_shop ps").
|
|
||||||
Select(`
|
|
||||||
ps.id_product AS id_product,
|
|
||||||
pl.name AS name,
|
|
||||||
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(pl.description_short, '<[^>]*>', ' '), '[[:space:]]+', ' ')) AS description,
|
|
||||||
p.ean13,
|
|
||||||
p.reference,
|
|
||||||
ps.price,
|
|
||||||
ps.id_category_default AS id_category,
|
|
||||||
cl.name AS cat_name,
|
|
||||||
cl.link_rewrite AS l_rew,
|
|
||||||
COALESCE(vary.attributes, JSON_OBJECT()) AS attr,
|
|
||||||
COALESCE(feat.features, JSON_OBJECT()) AS feat,
|
|
||||||
img.id_image,
|
|
||||||
cat.category_ids,
|
|
||||||
(SELECT COUNT(*) FROM ps_product_attribute_shop pas2 WHERE pas2.id_product = ps.id_product AND pas2.id_shop = ?) AS variations
|
|
||||||
`, constdata.SHOP_ID).
|
|
||||||
Joins("JOIN ps_product p ON p.id_product = ps.id_product").
|
|
||||||
Joins("JOIN ps_product_lang pl ON pl.id_product = ps.id_product AND pl.id_shop = ? AND pl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
|
||||||
Joins("JOIN ps_category_lang cl ON cl.id_category = ps.id_category_default AND cl.id_shop = ? AND cl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
|
||||||
Joins("LEFT JOIN variations vary ON vary.id_product = ps.id_product").
|
|
||||||
Joins("LEFT JOIN features feat ON feat.id_product = ps.id_product").
|
|
||||||
Joins("LEFT JOIN images img ON img.id_product = ps.id_product").
|
|
||||||
Joins("LEFT JOIN categories cat ON cat.id_product = ps.id_product").
|
|
||||||
Joins("JOIN products_page pp ON pp.id_product = ps.id_product").
|
|
||||||
Where("ps.active = ?", 1).
|
|
||||||
Order("ps.id_product").
|
|
||||||
Clauses(exclause.With{CTEs: []exclause.CTE{
|
|
||||||
{
|
|
||||||
Name: "products_page",
|
|
||||||
Subquery: exclause.Subquery{
|
|
||||||
DB: db.Get().
|
|
||||||
Model(&dbmodel.PsProductShop{}).
|
|
||||||
Select("id_product, price").
|
|
||||||
Where("id_shop = ? AND active = 1", constdata.SHOP_ID).
|
|
||||||
Order("id_product").
|
|
||||||
Limit(limit).
|
|
||||||
Offset(offset),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "variation_attributes",
|
|
||||||
Subquery: exclause.Subquery{
|
|
||||||
DB: db.Get().
|
|
||||||
Table("ps_product_attribute_shop pas"). // <- explicit alias here
|
|
||||||
Select(`
|
|
||||||
pas.id_product,
|
|
||||||
pag.id_attribute_group AS attribute_name,
|
|
||||||
JSON_ARRAYAGG(DISTINCT pa.id_attribute) AS attribute_values
|
|
||||||
`).
|
|
||||||
Joins("JOIN ps_product_attribute_combination ppac ON ppac.id_product_attribute = pas.id_product_attribute").
|
|
||||||
Joins("JOIN ps_attribute pa ON pa.id_attribute = ppac.id_attribute").
|
|
||||||
Joins("JOIN ps_attribute_group pag ON pag.id_attribute_group = pa.id_attribute_group").
|
|
||||||
Where("pas.id_shop = ?", constdata.SHOP_ID).
|
|
||||||
Group("pas.id_product, pag.id_attribute_group"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "variations",
|
|
||||||
Subquery: exclause.Subquery{
|
|
||||||
DB: db.Get().
|
|
||||||
Table("variation_attributes").
|
|
||||||
Select("id_product, JSON_OBJECTAGG(attribute_name, attribute_values) AS attributes").
|
|
||||||
Group("id_product"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "features",
|
|
||||||
Subquery: exclause.Subquery{
|
|
||||||
DB: db.Get().
|
|
||||||
Table("ps_feature_product pfp"). // <- explicit alias
|
|
||||||
Select("pfp.id_product, JSON_OBJECTAGG(pfp.id_feature, pfp.id_feature_value) AS features").
|
|
||||||
Group("pfp.id_product"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "images",
|
|
||||||
Subquery: exclause.Subquery{
|
|
||||||
DB: db.Get().
|
|
||||||
Model(&dbmodel.PsImageShop{}).
|
|
||||||
Select("id_product, id_image").
|
|
||||||
Where("id_shop = ? AND cover = 1", constdata.SHOP_ID),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "categories",
|
|
||||||
Subquery: exclause.Subquery{
|
|
||||||
DB: db.Get().
|
|
||||||
Model(&dbmodel.PsCategoryProduct{}).
|
|
||||||
Select("id_product, JSON_ARRAYAGG(id_category) AS category_ids").
|
|
||||||
Group("id_product"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}).Find(&products).Error
|
|
||||||
|
|
||||||
return products, err
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,13 +2,29 @@ package productsRepo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"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/dbmodel"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/view"
|
||||||
|
"git.ma-al.com/goc_marek/gormcol"
|
||||||
|
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UIProductsRepo interface {
|
type UIProductsRepo interface {
|
||||||
GetJSON(p_id_product, p_id_shop, p_id_lang, p_id_customer, b2b_id_country, p_quantity int) (*json.RawMessage, error)
|
// GetJSON(p_id_product, p_id_shop, p_id_lang, p_id_customer, b2b_id_country, p_quantity int) (*json.RawMessage, error)
|
||||||
|
Find(id_lang uint, userID uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.ProductInList], error)
|
||||||
|
GetProductVariants(langID uint, productID uint, shopID uint, customerID uint, countryID uint, quantity uint) ([]view.ProductAttribute, error)
|
||||||
|
GetBase(p_id_product, p_id_shop, p_id_lang, p_id_customer uint) (view.Product, error)
|
||||||
|
GetPrice(p_id_product uint, productAttributeID *uint, p_id_shop uint, p_id_customer uint, p_id_country uint, p_quantity uint) (view.Price, error)
|
||||||
|
GetVariants(p_id_product, p_id_shop, p_id_lang, p_id_customer, p_id_country, p_quantity uint) ([]view.ProductAttribute, error)
|
||||||
|
AddToFavorites(userID uint, productID uint) error
|
||||||
|
RemoveFromFavorites(userID uint, productID uint) error
|
||||||
|
ExistsInFavorites(userID uint, productID uint) (bool, error)
|
||||||
|
ProductInDatabase(productID uint) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductsRepo struct{}
|
type ProductsRepo struct{}
|
||||||
@@ -17,23 +33,251 @@ func New() UIProductsRepo {
|
|||||||
return &ProductsRepo{}
|
return &ProductsRepo{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *ProductsRepo) GetJSON(p_id_product, p_id_shop, p_id_lang, p_id_customer, b2b_id_country, p_quantity int) (*json.RawMessage, error) {
|
func (repo *ProductsRepo) GetBase(p_id_product, p_id_shop, p_id_lang, p_id_customer uint) (view.Product, error) {
|
||||||
var productStr string // ← Scan as string first
|
var result view.Product
|
||||||
|
|
||||||
err := db.DB.Raw(`CALL get_full_product(?,?,?,?,?,?)`,
|
err := db.DB.Raw(`CALL get_product_base(?,?,?,?)`,
|
||||||
p_id_product, p_id_shop, p_id_lang, p_id_customer, b2b_id_country, p_quantity).
|
p_id_product, p_id_shop, p_id_lang, p_id_customer).
|
||||||
Scan(&productStr).
|
Scan(&result).Error
|
||||||
Error
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *ProductsRepo) GetPrice(
|
||||||
|
p_id_product uint, productAttributeID *uint, p_id_shop uint, p_id_customer uint, p_id_country uint, p_quantity uint,
|
||||||
|
) (view.Price, error) {
|
||||||
|
|
||||||
|
type row struct {
|
||||||
|
Price json.RawMessage `gorm:"column:price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var r row
|
||||||
|
err := db.DB.Raw(`
|
||||||
|
SELECT fn_product_price(?,?,?,?,?,?) AS price`,
|
||||||
|
p_id_product, p_id_shop, p_id_customer, p_id_country, p_quantity, productAttributeID).
|
||||||
|
Scan(&r).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return view.Price{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var temp struct {
|
||||||
|
Base json.Number `json:"base"`
|
||||||
|
FinalTaxExcl json.Number `json:"final_tax_excl"`
|
||||||
|
FinalTaxIncl json.Number `json:"final_tax_incl"`
|
||||||
|
TaxRate json.Number `json:"tax_rate"`
|
||||||
|
Priority json.Number `json:"priority"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(r.Price, &temp); err != nil {
|
||||||
|
return view.Price{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
price := view.Price{
|
||||||
|
Base: mustParseFloat(temp.Base),
|
||||||
|
FinalTaxExcl: mustParseFloat(temp.FinalTaxExcl),
|
||||||
|
FinalTaxIncl: mustParseFloat(temp.FinalTaxIncl),
|
||||||
|
TaxRate: mustParseFloat(temp.TaxRate),
|
||||||
|
Priority: mustParseInt(temp.Priority),
|
||||||
|
}
|
||||||
|
|
||||||
|
return price, nil
|
||||||
|
}
|
||||||
|
func mustParseFloat(n json.Number) float64 {
|
||||||
|
f, _ := n.Float64()
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParseInt(n json.Number) int {
|
||||||
|
i, _ := n.Int64()
|
||||||
|
return int(i)
|
||||||
|
}
|
||||||
|
func (repo *ProductsRepo) GetVariants(p_id_product, p_id_shop, p_id_lang, p_id_customer, p_id_country, p_quantity uint) ([]view.ProductAttribute, error) {
|
||||||
|
|
||||||
|
var results []view.ProductAttribute
|
||||||
|
|
||||||
|
err := db.DB.Raw(`
|
||||||
|
CALL get_product_variants(?,?,?,?,?,?)`,
|
||||||
|
p_id_product, p_id_shop, p_id_lang, p_id_customer, p_id_country, p_quantity).
|
||||||
|
Scan(&results).Error
|
||||||
|
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.ProductInList], error) {
|
||||||
|
query := db.DB.
|
||||||
|
Table("base_products AS bp").
|
||||||
|
Clauses(exclause.With{
|
||||||
|
CTEs: []exclause.CTE{
|
||||||
|
{
|
||||||
|
Name: "favorites",
|
||||||
|
Subquery: exclause.Subquery{
|
||||||
|
DB: db.DB.
|
||||||
|
Table("b2b_favorites").
|
||||||
|
Select(`
|
||||||
|
product_id AS product_id,
|
||||||
|
COUNT(*) > 0 AS is_favorite
|
||||||
|
`).
|
||||||
|
Where("user_id = ?", userID).
|
||||||
|
Group("product_id"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "new_product_days",
|
||||||
|
Subquery: exclause.Subquery{
|
||||||
|
DB: db.DB.
|
||||||
|
Table("ps_configuration").
|
||||||
|
Select("CAST(value AS SIGNED) AS days").
|
||||||
|
Where("name = ?", "PS_NB_DAYS_NEW_PRODUCT"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "variants",
|
||||||
|
Subquery: exclause.Subquery{
|
||||||
|
DB: db.DB.
|
||||||
|
Table("ps_product_attribute_shop").
|
||||||
|
Select("id_product, COUNT(*) AS variants_number").
|
||||||
|
Group("id_product"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "base_products",
|
||||||
|
Subquery: exclause.Subquery{
|
||||||
|
DB: db.DB.
|
||||||
|
Table(gormcol.Field.Tab(dbmodel.PsProductShopCols.Active)+" AS ps").
|
||||||
|
Select(`
|
||||||
|
ps.id_product AS product_id,
|
||||||
|
pl.name AS name,
|
||||||
|
ps.id_category_default AS category_id,
|
||||||
|
p.reference AS reference,
|
||||||
|
sa.quantity AS quantity,
|
||||||
|
COALESCE(f.is_favorite, 0) AS is_favorite,
|
||||||
|
CASE
|
||||||
|
WHEN ps.date_add >= DATE_SUB(
|
||||||
|
NOW(),
|
||||||
|
INTERVAL COALESCE(npd.days, 20) DAY
|
||||||
|
) AND ps.active = 1
|
||||||
|
THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END AS is_new
|
||||||
|
`).
|
||||||
|
Joins("JOIN "+dbmodel.PsProductCols.IDProduct.Tab()+" p ON p.id_product = ps.id_product").
|
||||||
|
Joins("JOIN ps_product_lang pl ON pl.id_product = ps.id_product AND pl.id_lang = ?", langID).
|
||||||
|
Joins("LEFT JOIN favorites f ON f.product_id = ps.id_product").
|
||||||
|
Joins("LEFT JOIN ps_stock_available sa ON sa.id_product = ps.id_product AND sa.id_product_attribute = 0").
|
||||||
|
Joins("LEFT JOIN new_product_days npd ON 1 = 1").
|
||||||
|
Where("ps.active = ?", 1).
|
||||||
|
Group("ps.id_product"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
Select(`
|
||||||
|
bp.product_id AS product_id,
|
||||||
|
bp.name AS name,
|
||||||
|
pl.link_rewrite AS link_rewrite,
|
||||||
|
CONCAT(?, '/', ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
|
||||||
|
cl.name AS category_name,
|
||||||
|
bp.reference AS reference,
|
||||||
|
COALESCE(v.variants_number, 0) AS variants_number,
|
||||||
|
bp.quantity AS quantity,
|
||||||
|
bp.is_favorite AS is_favorite,
|
||||||
|
bp.is_new AS is_new
|
||||||
|
`, config.Get().Image.ImagePrefix).
|
||||||
|
Joins("JOIN ps_product_lang pl ON pl.id_product = bp.product_id AND pl.id_lang = ?", langID).
|
||||||
|
Joins("JOIN ps_image_shop ims ON ims.id_product = bp.product_id AND ims.cover = 1").
|
||||||
|
Joins("JOIN ps_category_lang cl ON cl.id_category = bp.category_id AND cl.id_lang = ?", langID).
|
||||||
|
Joins("LEFT JOIN variants v ON v.id_product = bp.product_id").
|
||||||
|
Order("bp.product_id DESC")
|
||||||
|
|
||||||
|
query = query.Scopes(filt.All()...)
|
||||||
|
|
||||||
|
list, err := find.Paginate[model.ProductInList](langID, p, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *ProductsRepo) GetProductVariants(langID uint, productID uint, shopID uint, customerID uint, countryID uint, quantity uint) ([]view.ProductAttribute, error) {
|
||||||
|
var result []view.ProductAttribute
|
||||||
|
err := db.DB.
|
||||||
|
Raw(`
|
||||||
|
CALL get_product_attributes_with_price(?, ?, ?, ?, ?, ?)
|
||||||
|
`,
|
||||||
|
langID,
|
||||||
|
productID,
|
||||||
|
shopID,
|
||||||
|
customerID,
|
||||||
|
countryID,
|
||||||
|
quantity,
|
||||||
|
).
|
||||||
|
Scan(&result).Error
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: validate it's valid JSON
|
return result, nil
|
||||||
if !json.Valid([]byte(productStr)) {
|
}
|
||||||
return nil, fmt.Errorf("invalid json returned from stored procedure")
|
|
||||||
|
func (repo *ProductsRepo) PopulateProductPrice(product *model.ProductInList, targetCustomer *model.Customer, quantity int, shopID uint) error {
|
||||||
|
row := db.Get().Raw(
|
||||||
|
"CALL get_product_price(?, ?, ?, ?, ?)",
|
||||||
|
product.ProductID,
|
||||||
|
shopID,
|
||||||
|
targetCustomer.ID,
|
||||||
|
targetCustomer.CountryID,
|
||||||
|
quantity,
|
||||||
|
).Row()
|
||||||
|
|
||||||
|
var (
|
||||||
|
id uint
|
||||||
|
base float64
|
||||||
|
excl float64
|
||||||
|
incl float64
|
||||||
|
tax float64
|
||||||
|
)
|
||||||
|
|
||||||
|
err := row.Scan(&id, &base, &excl, &incl, &tax)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
raw := json.RawMessage(productStr)
|
product.PriceTaxExcl = excl
|
||||||
return &raw, nil
|
product.PriceTaxIncl = incl
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *ProductsRepo) AddToFavorites(userID uint, productID uint) error {
|
||||||
|
fav := model.B2bFavorite{
|
||||||
|
UserID: userID,
|
||||||
|
ProductID: productID,
|
||||||
|
}
|
||||||
|
return db.Get().Create(&fav).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *ProductsRepo) RemoveFromFavorites(userID uint, productID uint) error {
|
||||||
|
return db.Get().
|
||||||
|
Where("user_id = ? AND product_id = ?", userID, productID).
|
||||||
|
Delete(&model.B2bFavorite{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *ProductsRepo) ExistsInFavorites(userID uint, productID uint) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := db.Get().
|
||||||
|
Table("b2b_favorites").
|
||||||
|
Where("user_id = ? AND product_id = ?", userID, productID).
|
||||||
|
Count(&count).Error
|
||||||
|
return count >= 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *ProductsRepo) ProductInDatabase(productID uint) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := db.Get().
|
||||||
|
Table(dbmodel.TableNamePsProduct).
|
||||||
|
Where(dbmodel.PsProductCols.IDProduct.Col()+" = ?", productID).
|
||||||
|
Count(&count).Error
|
||||||
|
return count >= 1, err
|
||||||
}
|
}
|
||||||
|
|||||||
22
app/repos/rolesRepo/rolesRepo.go
Normal file
22
app/repos/rolesRepo/rolesRepo.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package roleRepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UIRolesRepo interface {
|
||||||
|
Get(id uint) (*model.Role, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RolesRepo struct{}
|
||||||
|
|
||||||
|
func New() UIRolesRepo {
|
||||||
|
return &RolesRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RolesRepo) Get(id uint) (*model.Role, error) {
|
||||||
|
var role model.Role
|
||||||
|
err := db.DB.First(&role, id).Error
|
||||||
|
return &role, err
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UIRoutesRepo interface {
|
type UIRoutesRepo interface {
|
||||||
GetRoutes(langId uint) ([]model.Route, error)
|
GetRoutes(langId uint, roleId uint) ([]model.Route, error)
|
||||||
GetTopMenu(id uint, roleId uint) ([]model.B2BTopMenu, error)
|
GetTopMenu(id uint, roleId uint) ([]model.B2BTopMenu, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,13 +17,18 @@ func New() UIRoutesRepo {
|
|||||||
return &RoutesRepo{}
|
return &RoutesRepo{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RoutesRepo) GetRoutes(langId uint) ([]model.Route, error) {
|
func (p *RoutesRepo) GetRoutes(langId uint, roleId uint) ([]model.Route, error) {
|
||||||
routes := []model.Route{}
|
routes := []model.Route{}
|
||||||
err := db.DB.Find(&routes, model.Route{Active: nullable.GetNil(true)}).Error
|
|
||||||
if err != nil {
|
err := db.
|
||||||
return nil, err
|
Get().
|
||||||
}
|
Model(model.Route{}).
|
||||||
return routes, nil
|
Joins("JOIN b2b_route_roles rr ON rr.route_id = b2b_routes.id").
|
||||||
|
Where(model.Route{Active: nullable.GetNil(true)}).
|
||||||
|
Where("rr.role_id = ?", roleId).
|
||||||
|
Find(&routes).Error
|
||||||
|
|
||||||
|
return routes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RoutesRepo) GetTopMenu(langId uint, roleId uint) ([]model.B2BTopMenu, error) {
|
func (p *RoutesRepo) GetTopMenu(langId uint, roleId uint) ([]model.B2BTopMenu, error) {
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"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/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"
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
|
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SearchProxyResponse struct {
|
type SearchProxyResponse struct {
|
||||||
@@ -17,6 +21,7 @@ type SearchProxyResponse struct {
|
|||||||
|
|
||||||
type UISearchRepo interface {
|
type UISearchRepo interface {
|
||||||
Search(index string, body []byte) (*SearchProxyResponse, error)
|
Search(index string, body []byte) (*SearchProxyResponse, error)
|
||||||
|
GetMeiliProducts(id_lang uint, offset, limit int) ([]model.MeiliSearchProduct, error)
|
||||||
GetIndexSettings(index string) (*SearchProxyResponse, error)
|
GetIndexSettings(index string) (*SearchProxyResponse, error)
|
||||||
GetRoutes(langId uint) ([]model.Route, error)
|
GetRoutes(langId uint) ([]model.Route, error)
|
||||||
}
|
}
|
||||||
@@ -32,12 +37,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 +60,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{}
|
||||||
@@ -80,3 +85,108 @@ func (r *SearchRepo) doRequest(method, url string, body []byte) (*SearchProxyRes
|
|||||||
func (r *SearchRepo) GetRoutes(langId uint) ([]model.Route, error) {
|
func (r *SearchRepo) GetRoutes(langId uint) ([]model.Route, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMeiliProductsProducts returns a batch of products with LIMIT/OFFSET pagination
|
||||||
|
// The scanning is done inside the repo to keep the service layer cleaner
|
||||||
|
func (r *SearchRepo) GetMeiliProducts(id_lang uint, offset, limit int) ([]model.MeiliSearchProduct, error) {
|
||||||
|
|
||||||
|
var products []model.MeiliSearchProduct
|
||||||
|
|
||||||
|
err := db.Get().
|
||||||
|
Table("ps_product_shop ps").
|
||||||
|
Select(`
|
||||||
|
ps.id_product AS id_product,
|
||||||
|
pl.name AS name,
|
||||||
|
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(pl.description_short, '<[^>]*>', ' '), '[[:space:]]+', ' ')) AS description,
|
||||||
|
p.ean13,
|
||||||
|
p.reference,
|
||||||
|
ps.price,
|
||||||
|
ps.id_category_default AS id_category,
|
||||||
|
cl.name AS cat_name,
|
||||||
|
cl.link_rewrite AS l_rew,
|
||||||
|
COALESCE(vary.attributes, JSON_OBJECT()) AS attr,
|
||||||
|
COALESCE(feat.features, JSON_OBJECT()) AS feat,
|
||||||
|
img.id_image,
|
||||||
|
cat.category_ids,
|
||||||
|
(SELECT COUNT(*) FROM ps_product_attribute_shop pas2 WHERE pas2.id_product = ps.id_product AND pas2.id_shop = ?) AS variations
|
||||||
|
`, constdata.SHOP_ID).
|
||||||
|
Joins("JOIN ps_product p ON p.id_product = ps.id_product").
|
||||||
|
Joins("JOIN ps_product_lang pl ON pl.id_product = ps.id_product AND pl.id_shop = ? AND pl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
||||||
|
Joins("JOIN ps_category_lang cl ON cl.id_category = ps.id_category_default AND cl.id_shop = ? AND cl.id_lang = ?", constdata.SHOP_ID, id_lang).
|
||||||
|
Joins("LEFT JOIN variations vary ON vary.id_product = ps.id_product").
|
||||||
|
Joins("LEFT JOIN features feat ON feat.id_product = ps.id_product").
|
||||||
|
Joins("LEFT JOIN images img ON img.id_product = ps.id_product").
|
||||||
|
Joins("LEFT JOIN categories cat ON cat.id_product = ps.id_product").
|
||||||
|
Joins("JOIN products_page pp ON pp.id_product = ps.id_product").
|
||||||
|
Where("ps.active = ?", 1).
|
||||||
|
Order("ps.id_product").
|
||||||
|
Clauses(exclause.With{CTEs: []exclause.CTE{
|
||||||
|
{
|
||||||
|
Name: "products_page",
|
||||||
|
Subquery: exclause.Subquery{
|
||||||
|
DB: db.Get().
|
||||||
|
Model(&dbmodel.PsProductShop{}).
|
||||||
|
Select("id_product, price").
|
||||||
|
Where("id_shop = ? AND active = 1", constdata.SHOP_ID).
|
||||||
|
Order("id_product").
|
||||||
|
Limit(limit).
|
||||||
|
Offset(offset),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "variation_attributes",
|
||||||
|
Subquery: exclause.Subquery{
|
||||||
|
DB: db.Get().
|
||||||
|
Table("ps_product_attribute_shop pas"). // <- explicit alias here
|
||||||
|
Select(`
|
||||||
|
pas.id_product,
|
||||||
|
pag.id_attribute_group AS attribute_name,
|
||||||
|
JSON_ARRAYAGG(DISTINCT pa.id_attribute) AS attribute_values
|
||||||
|
`).
|
||||||
|
Joins("JOIN ps_product_attribute_combination ppac ON ppac.id_product_attribute = pas.id_product_attribute").
|
||||||
|
Joins("JOIN ps_attribute pa ON pa.id_attribute = ppac.id_attribute").
|
||||||
|
Joins("JOIN ps_attribute_group pag ON pag.id_attribute_group = pa.id_attribute_group").
|
||||||
|
Where("pas.id_shop = ?", constdata.SHOP_ID).
|
||||||
|
Group("pas.id_product, pag.id_attribute_group"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "variations",
|
||||||
|
Subquery: exclause.Subquery{
|
||||||
|
DB: db.Get().
|
||||||
|
Table("variation_attributes").
|
||||||
|
Select("id_product, JSON_OBJECTAGG(attribute_name, attribute_values) AS attributes").
|
||||||
|
Group("id_product"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "features",
|
||||||
|
Subquery: exclause.Subquery{
|
||||||
|
DB: db.Get().
|
||||||
|
Table("ps_feature_product pfp"). // <- explicit alias
|
||||||
|
Select("pfp.id_product, JSON_OBJECTAGG(pfp.id_feature, pfp.id_feature_value) AS features").
|
||||||
|
Group("pfp.id_product"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "images",
|
||||||
|
Subquery: exclause.Subquery{
|
||||||
|
DB: db.Get().
|
||||||
|
Model(&dbmodel.PsImageShop{}).
|
||||||
|
Select("id_product, id_image").
|
||||||
|
Where("id_shop = ? AND cover = 1", constdata.SHOP_ID),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "categories",
|
||||||
|
Subquery: exclause.Subquery{
|
||||||
|
DB: db.Get().
|
||||||
|
Model(&dbmodel.PsCategoryProduct{}).
|
||||||
|
Select("id_product, JSON_ARRAYAGG(id_category) AS category_ids").
|
||||||
|
Group("id_product"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}).Find(&products).Error
|
||||||
|
|
||||||
|
return products, err
|
||||||
|
}
|
||||||
|
|||||||
247
app/repos/specificPriceRepo/specificPriceRepo.go
Normal file
247
app/repos/specificPriceRepo/specificPriceRepo.go
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
package specificPriceRepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UISpecificPriceRepo interface {
|
||||||
|
Create(ctx context.Context, pr *model.SpecificPrice) error
|
||||||
|
Update(ctx context.Context, pr *model.SpecificPrice) error
|
||||||
|
Delete(ctx context.Context, id uint64) error
|
||||||
|
GetByID(ctx context.Context, id uint64) (*model.SpecificPrice, error)
|
||||||
|
List(ctx context.Context) ([]*model.SpecificPrice, error)
|
||||||
|
SetActive(ctx context.Context, id uint64, active bool) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpecificPriceRepo struct{}
|
||||||
|
|
||||||
|
func New() UISpecificPriceRepo {
|
||||||
|
return &SpecificPriceRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *SpecificPriceRepo) Create(ctx context.Context, pr *model.SpecificPrice) error {
|
||||||
|
return db.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
now := time.Now()
|
||||||
|
pr.CreatedAt = &now
|
||||||
|
|
||||||
|
if err := tx.Create(pr).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo.insertRelations(tx, pr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *SpecificPriceRepo) Update(ctx context.Context, pr *model.SpecificPrice) error {
|
||||||
|
return db.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
now := time.Now()
|
||||||
|
pr.UpdatedAt = &now
|
||||||
|
|
||||||
|
if err := tx.Save(pr).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo.clearRelations(tx, pr.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo.insertRelations(tx, pr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *SpecificPriceRepo) GetByID(ctx context.Context, id uint64) (*model.SpecificPrice, error) {
|
||||||
|
var pr model.SpecificPrice
|
||||||
|
err := db.DB.WithContext(ctx).Where("id = ?", id).First(&pr).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo.loadRelations(ctx, &pr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *SpecificPriceRepo) List(ctx context.Context) ([]*model.SpecificPrice, error) {
|
||||||
|
var specificPrices []*model.SpecificPrice
|
||||||
|
err := db.DB.WithContext(ctx).Find(&specificPrices).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range specificPrices {
|
||||||
|
if err := repo.loadRelations(ctx, specificPrices[i]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return specificPrices, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *SpecificPriceRepo) SetActive(ctx context.Context, id uint64, active bool) error {
|
||||||
|
return db.DB.WithContext(ctx).Model(&model.SpecificPrice{}).Where("id = ?", id).Update("is_active", active).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *SpecificPriceRepo) Delete(ctx context.Context, id uint64) error {
|
||||||
|
return db.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
if err := tx.Exec("DELETE FROM b2b_specific_price_product WHERE b2b_specific_price_id = ?", id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Exec("DELETE FROM b2b_specific_price_category WHERE b2b_specific_price_id = ?", id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Exec("DELETE FROM b2b_specific_price_product_attribute WHERE b2b_specific_price_id = ?", id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Exec("DELETE FROM b2b_specific_price_country WHERE b2b_specific_price_id = ?", id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Exec("DELETE FROM b2b_specific_price_customer WHERE b2b_specific_price_id = ?", id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Delete(&model.SpecificPrice{}, "id = ?", id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *SpecificPriceRepo) insertRelations(tx *gorm.DB, pr *model.SpecificPrice) error {
|
||||||
|
if len(pr.ProductIDs) > 0 {
|
||||||
|
for _, productID := range pr.ProductIDs {
|
||||||
|
if err := tx.Exec(`
|
||||||
|
INSERT INTO b2b_specific_price_product (b2b_specific_price_id, id_product) VALUES (?, ?)
|
||||||
|
`, pr.ID, productID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pr.CategoryIDs) > 0 {
|
||||||
|
for _, categoryID := range pr.CategoryIDs {
|
||||||
|
if err := tx.Exec(`
|
||||||
|
INSERT INTO b2b_specific_price_category (b2b_specific_price_id, id_category) VALUES (?, ?)
|
||||||
|
`, pr.ID, categoryID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pr.ProductAttributeIDs) > 0 {
|
||||||
|
for _, attrID := range pr.ProductAttributeIDs {
|
||||||
|
if err := tx.Exec(`
|
||||||
|
INSERT INTO b2b_specific_price_product_attribute (b2b_specific_price_id, id_product_attribute) VALUES (?, ?)
|
||||||
|
`, pr.ID, attrID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pr.CountryIDs) > 0 {
|
||||||
|
for _, countryID := range pr.CountryIDs {
|
||||||
|
if err := tx.Exec(`
|
||||||
|
INSERT INTO b2b_specific_price_country (b2b_specific_price_id, b2b_id_country) VALUES (?, ?)
|
||||||
|
`, pr.ID, countryID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pr.CustomerIDs) > 0 {
|
||||||
|
for _, customerID := range pr.CustomerIDs {
|
||||||
|
if err := tx.Exec(`
|
||||||
|
INSERT INTO b2b_specific_price_customer (b2b_specific_price_id, b2b_id_customer) VALUES (?, ?)
|
||||||
|
`, pr.ID, customerID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *SpecificPriceRepo) clearRelations(tx *gorm.DB, id uint64) error {
|
||||||
|
if err := tx.Exec("DELETE FROM b2b_specific_price_product WHERE b2b_specific_price_id = ?", id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Exec("DELETE FROM b2b_specific_price_category WHERE b2b_specific_price_id = ?", id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Exec("DELETE FROM b2b_specific_price_product_attribute WHERE b2b_specific_price_id = ?", id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Exec("DELETE FROM b2b_specific_price_country WHERE b2b_specific_price_id = ?", id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Exec("DELETE FROM b2b_specific_price_customer WHERE b2b_specific_price_id = ?", id).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *SpecificPriceRepo) loadRelations(ctx context.Context, pr *model.SpecificPrice) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var productIDs []struct {
|
||||||
|
IDProduct uint64 `gorm:"column:id_product"`
|
||||||
|
}
|
||||||
|
if err = db.DB.WithContext(ctx).Table("b2b_specific_price_product").Where("b2b_specific_price_id = ?", pr.ID).Select("id_product").Scan(&productIDs).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, p := range productIDs {
|
||||||
|
pr.ProductIDs = append(pr.ProductIDs, p.IDProduct)
|
||||||
|
}
|
||||||
|
|
||||||
|
var categoryIDs []struct {
|
||||||
|
IDCategory uint64 `gorm:"column:id_category"`
|
||||||
|
}
|
||||||
|
if err = db.DB.WithContext(ctx).Table("b2b_specific_price_category").Where("b2b_specific_price_id = ?", pr.ID).Select("id_category").Scan(&categoryIDs).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, c := range categoryIDs {
|
||||||
|
pr.CategoryIDs = append(pr.CategoryIDs, c.IDCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
var attrIDs []struct {
|
||||||
|
IDAttr uint64 `gorm:"column:id_product_attribute"`
|
||||||
|
}
|
||||||
|
if err = db.DB.WithContext(ctx).Table("b2b_specific_price_product_attribute").Where("b2b_specific_price_id = ?", pr.ID).Select("id_product_attribute").Scan(&attrIDs).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, a := range attrIDs {
|
||||||
|
pr.ProductAttributeIDs = append(pr.ProductAttributeIDs, a.IDAttr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var countryIDs []struct {
|
||||||
|
IDCountry uint64 `gorm:"column:b2b_id_country"`
|
||||||
|
}
|
||||||
|
if err = db.DB.WithContext(ctx).Table("b2b_specific_price_country").Where("b2b_specific_price_id = ?", pr.ID).Select("b2b_id_country").Scan(&countryIDs).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, c := range countryIDs {
|
||||||
|
pr.CountryIDs = append(pr.CountryIDs, c.IDCountry)
|
||||||
|
}
|
||||||
|
|
||||||
|
var customerIDs []struct {
|
||||||
|
IDCustomer uint64 `gorm:"column:b2b_id_customer"`
|
||||||
|
}
|
||||||
|
if err = db.DB.WithContext(ctx).Table("b2b_specific_price_customer").Where("b2b_specific_price_id = ?", pr.ID).Select("b2b_id_customer").Scan(&customerIDs).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, c := range customerIDs {
|
||||||
|
pr.CustomerIDs = append(pr.CustomerIDs, c.IDCustomer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
145
app/service/addressesService/addressesService.go
Normal file
145
app/service/addressesService/addressesService.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package addressesService
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/repos/addressesRepo"
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AddressesService struct {
|
||||||
|
repo addressesRepo.UIAddressesRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *AddressesService {
|
||||||
|
return &AddressesService{
|
||||||
|
repo: addressesRepo.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AddressesService) GetTemplate(country_id uint) (model.AddressUnparsed, error) {
|
||||||
|
switch country_id {
|
||||||
|
|
||||||
|
case 1: // Poland
|
||||||
|
return model.AddressPL{}, nil
|
||||||
|
|
||||||
|
case 2: // Great Britain
|
||||||
|
return model.AddressGB{}, nil
|
||||||
|
|
||||||
|
case 3: // Czech Republic
|
||||||
|
return model.AddressCZ{}, nil
|
||||||
|
|
||||||
|
case 4: // Germany
|
||||||
|
return model.AddressDE{}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, responseErrors.ErrInvalidCountryID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AddressesService) AddNewAddress(user_id uint, address_info string, country_id uint) error {
|
||||||
|
amt, err := s.repo.UserAddressesAmt(user_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if amt >= constdata.MAX_AMOUNT_OF_ADDRESSES_PER_USER {
|
||||||
|
return responseErrors.ErrMaxAmtOfAddressesReached
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.ValidateAddressJson(address_info, country_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.AddNewAddress(user_id, address_info, country_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// country_id = 0 means that country_id remains unchanged
|
||||||
|
func (s *AddressesService) ModifyAddress(user_id uint, address_id uint, address_info string, country_id uint) error {
|
||||||
|
amt, err := s.repo.UserHasAddress(user_id, address_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if amt != 1 {
|
||||||
|
return responseErrors.ErrUserHasNoSuchAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.ValidateAddressJson(address_info, country_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.UpdateAddress(user_id, address_id, address_info, country_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AddressesService) RetrieveAddresses(user_id uint) (*[]model.Address, error) {
|
||||||
|
addresses, err := s.repo.RetrieveAddresses(user_id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(*addresses); i++ {
|
||||||
|
address_unparsed, err := s.ValidateAddressJson((*addresses)[i].AddressString, (*addresses)[i].CountryID)
|
||||||
|
// log such errors
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
(*addresses)[i].AddressUnparsed = &address_unparsed
|
||||||
|
}
|
||||||
|
|
||||||
|
return addresses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AddressesService) DeleteAddress(user_id uint, address_id uint) error {
|
||||||
|
amt, err := s.repo.UserHasAddress(user_id, address_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if amt != 1 {
|
||||||
|
return responseErrors.ErrUserHasNoSuchAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.DeleteAddress(user_id, address_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateAddressJson makes sure that the info string represents a valid json of address in given country
|
||||||
|
func (s *AddressesService) ValidateAddressJson(info string, country_id uint) (model.AddressUnparsed, error) {
|
||||||
|
dec := json.NewDecoder(strings.NewReader(info))
|
||||||
|
dec.DisallowUnknownFields()
|
||||||
|
|
||||||
|
switch country_id {
|
||||||
|
|
||||||
|
case 1: // Poland
|
||||||
|
var address model.AddressPL
|
||||||
|
if err := dec.Decode(&address); err != nil {
|
||||||
|
return address, responseErrors.ErrInvalidAddressJSON
|
||||||
|
}
|
||||||
|
return address, nil
|
||||||
|
|
||||||
|
case 2: // Great Britain
|
||||||
|
var address model.AddressGB
|
||||||
|
if err := dec.Decode(&address); err != nil {
|
||||||
|
return address, responseErrors.ErrInvalidAddressJSON
|
||||||
|
}
|
||||||
|
return address, nil
|
||||||
|
|
||||||
|
case 3: // Czech Republic
|
||||||
|
var address model.AddressCZ
|
||||||
|
if err := dec.Decode(&address); err != nil {
|
||||||
|
return address, responseErrors.ErrInvalidAddressJSON
|
||||||
|
}
|
||||||
|
return address, nil
|
||||||
|
|
||||||
|
case 4: // Germany
|
||||||
|
var address model.AddressDE
|
||||||
|
if err := dec.Decode(&address); err != nil {
|
||||||
|
return address, responseErrors.ErrInvalidAddressJSON
|
||||||
|
}
|
||||||
|
return address, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, responseErrors.ErrInvalidCountryID
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ 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/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/repos/customerRepo"
|
||||||
|
roleRepo "git.ma-al.com/goc_daniel/b2b/app/repos/rolesRepo"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/emailService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/emailService"
|
||||||
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/responseErrors"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
@@ -19,6 +21,7 @@ import (
|
|||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JWTClaims represents the JWT claims
|
// JWTClaims represents the JWT claims
|
||||||
@@ -38,6 +41,8 @@ type AuthService struct {
|
|||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
config *config.AuthConfig
|
config *config.AuthConfig
|
||||||
email *emailService.EmailService
|
email *emailService.EmailService
|
||||||
|
customerRepo customerRepo.UICustomerRepo
|
||||||
|
roleRepo roleRepo.UIRolesRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthService creates a new AuthService instance
|
// NewAuthService creates a new AuthService instance
|
||||||
@@ -46,6 +51,8 @@ func NewAuthService() *AuthService {
|
|||||||
db: db.Get(),
|
db: db.Get(),
|
||||||
config: &config.Get().Auth,
|
config: &config.Get().Auth,
|
||||||
email: emailService.NewEmailService(),
|
email: emailService.NewEmailService(),
|
||||||
|
customerRepo: customerRepo.New(),
|
||||||
|
roleRepo: roleRepo.New(),
|
||||||
}
|
}
|
||||||
// Auto-migrate the refresh_tokens table
|
// Auto-migrate the refresh_tokens table
|
||||||
if svc.db != nil {
|
if svc.db != nil {
|
||||||
@@ -83,6 +90,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)
|
||||||
@@ -144,7 +160,6 @@ func (s *AuthService) Register(req *model.RegisterRequest) error {
|
|||||||
Password: string(hashedPassword),
|
Password: string(hashedPassword),
|
||||||
FirstName: req.FirstName,
|
FirstName: req.FirstName,
|
||||||
LastName: req.LastName,
|
LastName: req.LastName,
|
||||||
Role: model.Role{},
|
|
||||||
Provider: model.ProviderLocal,
|
Provider: model.ProviderLocal,
|
||||||
IsActive: false,
|
IsActive: false,
|
||||||
EmailVerified: false,
|
EmailVerified: false,
|
||||||
@@ -422,7 +437,7 @@ func (s *AuthService) RevokeAllRefreshTokens(userID uint) {
|
|||||||
// GetUserByID retrieves a user by ID
|
// GetUserByID retrieves a user by ID
|
||||||
func (s *AuthService) GetUserByID(userID uint) (*model.Customer, error) {
|
func (s *AuthService) GetUserByID(userID uint) (*model.Customer, error) {
|
||||||
var user model.Customer
|
var user model.Customer
|
||||||
if err := s.db.Preload("Role.Permissions").First(&user, userID).Error; err != nil {
|
if err := s.db.Preload("Role.Permissions").Preload(clause.Associations).First(&user, userID).Error; err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, responseErrors.ErrUserNotFound
|
return nil, responseErrors.ErrUserNotFound
|
||||||
}
|
}
|
||||||
@@ -443,6 +458,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
|
||||||
|
|||||||
@@ -108,26 +108,32 @@ func (s *AuthService) HandleGoogleCallback(code string) (*model.AuthResponse, st
|
|||||||
// findOrCreateGoogleUser finds an existing user by Google provider ID or email,
|
// findOrCreateGoogleUser finds an existing user by Google provider ID or email,
|
||||||
// or creates a new one.
|
// or creates a new one.
|
||||||
func (s *AuthService) findOrCreateGoogleUser(info *view.GoogleUserInfo) (*model.Customer, error) {
|
func (s *AuthService) findOrCreateGoogleUser(info *view.GoogleUserInfo) (*model.Customer, error) {
|
||||||
var user model.Customer
|
var user *model.Customer
|
||||||
|
|
||||||
// Try to find by provider + provider_id
|
// Try to find by provider + provider_id
|
||||||
err := s.db.Where("provider = ? AND provider_id = ?", model.ProviderGoogle, info.ID).First(&user).Error
|
user, err := s.customerRepo.GetByExternalProviderId(model.ProviderGoogle, info.ID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Update avatar in case it changed
|
// Update avatar in case it changed
|
||||||
user.AvatarURL = info.Picture
|
user.AvatarURL = info.Picture
|
||||||
s.db.Save(&user)
|
err = s.customerRepo.Save(user)
|
||||||
return &user, nil
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find by email (user may have registered locally before)
|
// Try to find by email (user may have registered locally before)
|
||||||
err = s.db.Where("email = ?", info.Email).First(&user).Error
|
user, err = s.customerRepo.GetByEmail(info.Email)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Link Google provider to existing account
|
// Link Google provider to existing account
|
||||||
user.Provider = model.ProviderGoogle
|
user.Provider = model.ProviderGoogle
|
||||||
user.ProviderID = info.ID
|
user.ProviderID = info.ID
|
||||||
user.AvatarURL = info.Picture
|
user.AvatarURL = info.Picture
|
||||||
user.IsActive = true
|
user.IsActive = true
|
||||||
s.db.Save(&user)
|
err = s.customerRepo.Save(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// If email has not been verified yet, send email to admin.
|
// If email has not been verified yet, send email to admin.
|
||||||
if !user.EmailVerified {
|
if !user.EmailVerified {
|
||||||
@@ -139,7 +145,7 @@ func (s *AuthService) findOrCreateGoogleUser(info *view.GoogleUserInfo) (*model.
|
|||||||
}
|
}
|
||||||
user.EmailVerified = true
|
user.EmailVerified = true
|
||||||
|
|
||||||
return &user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new user
|
// Create new user
|
||||||
@@ -148,16 +154,16 @@ func (s *AuthService) findOrCreateGoogleUser(info *view.GoogleUserInfo) (*model.
|
|||||||
FirstName: info.GivenName,
|
FirstName: info.GivenName,
|
||||||
LastName: info.FamilyName,
|
LastName: info.FamilyName,
|
||||||
Provider: model.ProviderGoogle,
|
Provider: model.ProviderGoogle,
|
||||||
|
RoleID: 1, // user
|
||||||
ProviderID: info.ID,
|
ProviderID: info.ID,
|
||||||
AvatarURL: info.Picture,
|
AvatarURL: info.Picture,
|
||||||
Role: model.Role{},
|
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
EmailVerified: true,
|
EmailVerified: true,
|
||||||
LangID: 2, // default is english
|
LangID: 2, // default is english
|
||||||
CountryID: 2, // default is England
|
CountryID: 2, // default is England
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.db.Create(&newUser).Error; err != nil {
|
if err := s.customerRepo.Create(&newUser); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create user: %w", err)
|
return nil, fmt.Errorf("failed to create user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +176,13 @@ func (s *AuthService) findOrCreateGoogleUser(info *view.GoogleUserInfo) (*model.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var role *model.Role
|
||||||
|
role, err = s.roleRepo.Get(newUser.RoleID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newUser.Role = role
|
||||||
|
|
||||||
return &newUser, nil
|
return &newUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,12 +34,24 @@ func (s *CartsService) CreateNewCart(user_id uint) (model.CustomerCart, error) {
|
|||||||
return cart, nil
|
return cart, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CartsService) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
func (s *CartsService) RemoveCart(user_id uint, cart_id uint) error {
|
||||||
amt, err := s.repo.UserHasCart(user_id, cart_id)
|
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if amt != 1 {
|
if !exists {
|
||||||
|
return responseErrors.ErrUserHasNoSuchCart
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.RemoveCart(user_id, cart_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CartsService) UpdateCartName(user_id uint, cart_id uint, new_name string) error {
|
||||||
|
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
return responseErrors.ErrUserHasNoSuchCart
|
return responseErrors.ErrUserHasNoSuchCart
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,33 +63,45 @@ func (s *CartsService) RetrieveCartsInfo(user_id uint) ([]model.CustomerCart, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error) {
|
func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error) {
|
||||||
amt, err := s.repo.UserHasCart(user_id, cart_id)
|
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if amt != 1 {
|
if !exists {
|
||||||
return nil, responseErrors.ErrUserHasNoSuchCart
|
return nil, responseErrors.ErrUserHasNoSuchCart
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.repo.RetrieveCart(user_id, cart_id)
|
return s.repo.RetrieveCart(user_id, cart_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error {
|
func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount int, set_amount bool) error {
|
||||||
amt, err := s.repo.UserHasCart(user_id, cart_id)
|
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if amt != 1 {
|
if !exists {
|
||||||
return responseErrors.ErrUserHasNoSuchCart
|
return responseErrors.ErrUserHasNoSuchCart
|
||||||
}
|
}
|
||||||
|
|
||||||
amt, err = s.repo.CheckProductExists(product_id, product_attribute_id)
|
exists, err = s.repo.CheckProductExists(product_id, product_attribute_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if amt != 1 {
|
if !exists {
|
||||||
return responseErrors.ErrProductOrItsVariationDoesNotExist
|
return responseErrors.ErrProductOrItsVariationDoesNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.repo.AddProduct(user_id, cart_id, product_id, product_attribute_id, amount)
|
return s.repo.AddProduct(cart_id, product_id, product_attribute_id, uint(amount), set_amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CartsService) RemoveProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint) error {
|
||||||
|
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return responseErrors.ErrUserHasNoSuchCart
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.RemoveProduct(cart_id, product_id, product_attribute_id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package customerService
|
|||||||
import (
|
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/repos/customerRepo"
|
"git.ma-al.com/goc_daniel/b2b/app/repos/customerRepo"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CustomerService struct {
|
type CustomerService struct {
|
||||||
@@ -18,3 +20,7 @@ func New() *CustomerService {
|
|||||||
func (s *CustomerService) GetById(id uint) (*model.Customer, error) {
|
func (s *CustomerService) GetById(id uint) (*model.Customer, error) {
|
||||||
return s.repo.Get(id)
|
return s.repo.Get(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *CustomerService) Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error) {
|
||||||
|
return s.repo.Find(langId, p, filt, search)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
@@ -116,6 +117,18 @@ func (s *EmailService) SendNewUserAdminNotification(userEmail, userName, baseURL
|
|||||||
return s.SendEmail(s.config.AdminEmail, subject, body)
|
return s.SendEmail(s.config.AdminEmail, subject, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendNewOrderPlacedNotification sends an email to admin when new order is placed
|
||||||
|
func (s *EmailService) SendNewOrderPlacedNotification(userID uint) error {
|
||||||
|
if s.config.AdminEmail == "" {
|
||||||
|
return nil // No admin email configured
|
||||||
|
}
|
||||||
|
|
||||||
|
subject := "New Order Created"
|
||||||
|
body := s.newOrderPlacedTemplate(userID)
|
||||||
|
|
||||||
|
return s.SendEmail(s.config.AdminEmail, subject, body)
|
||||||
|
}
|
||||||
|
|
||||||
// verificationEmailTemplate returns the HTML template for email verification
|
// verificationEmailTemplate returns the HTML template for email verification
|
||||||
func (s *EmailService) verificationEmailTemplate(name, verificationURL string, langID uint) string {
|
func (s *EmailService) verificationEmailTemplate(name, verificationURL string, langID uint) string {
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
@@ -133,6 +146,13 @@ 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// newUserAdminNotificationTemplate returns the HTML template for admin notification
|
||||||
|
func (s *EmailService) newOrderPlacedTemplate(userID uint) string {
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
emails.EmailNewOrderPlacedWrapper(view.EmailLayout[view.EmailNewOrderPlacedData]{LangID: constdata.ADMIN_NOTIFICATION_LANGUAGE, Data: view.EmailNewOrderPlacedData{UserID: userID}}).Render(context.Background(), &buf)
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
package listService
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/repos/listRepo"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListService struct {
|
|
||||||
listRepo listRepo.UIListRepo
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *ListService {
|
|
||||||
return &ListService{
|
|
||||||
listRepo: listRepo.New(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ListService) ListProducts(id_lang uint, p find.Paging, filters *filters.FiltersList) (find.Found[model.ProductInList], error) {
|
|
||||||
return s.listRepo.ListProducts(id_lang, p, filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ListService) ListUsers(id_lang uint, p find.Paging, filters *filters.FiltersList) (find.Found[model.UserInList], error) {
|
|
||||||
return s.listRepo.ListUsers(id_lang, p, filters)
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,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/model"
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/repos/productDescriptionRepo"
|
searchrepo "git.ma-al.com/goc_daniel/b2b/app/repos/searchRepo"
|
||||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
"github.com/meilisearch/meilisearch-go"
|
"github.com/meilisearch/meilisearch-go"
|
||||||
)
|
)
|
||||||
@@ -20,20 +20,20 @@ type MeiliIndexSettings struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MeiliService struct {
|
type MeiliService struct {
|
||||||
productDescriptionRepo productDescriptionRepo.UIProductDescriptionRepo
|
searchRepo searchrepo.UISearchRepo
|
||||||
meiliClient meilisearch.ServiceManager
|
meiliClient meilisearch.ServiceManager
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
||||||
meiliClient: client,
|
meiliClient: client,
|
||||||
productDescriptionRepo: productDescriptionRepo.New(),
|
searchRepo: searchrepo.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ func (s *MeiliService) CreateIndex(id_lang uint) error {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
// Get batch of products from repo (includes scanning)
|
// Get batch of products from repo (includes scanning)
|
||||||
products, err := s.productDescriptionRepo.GetMeiliProducts(id_lang, offset, batchSize)
|
products, err := s.searchRepo.GetMeiliProducts(id_lang, offset, batchSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get products batch at offset %d: %w", offset, err)
|
return fmt.Errorf("failed to get products batch at offset %d: %w", offset, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,31 +3,45 @@ package menuService
|
|||||||
import (
|
import (
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"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/repos/categoriesRepo"
|
categoryrepo "git.ma-al.com/goc_daniel/b2b/app/repos/categoryRepo"
|
||||||
routesRepo "git.ma-al.com/goc_daniel/b2b/app/repos/routesRepo"
|
routesRepo "git.ma-al.com/goc_daniel/b2b/app/repos/routesRepo"
|
||||||
|
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/responseErrors"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MenuService struct {
|
type MenuService struct {
|
||||||
categoriesRepo categoriesRepo.UICategoriesRepo
|
categoryRepo categoryrepo.UICategoryRepo
|
||||||
routesRepo routesRepo.UIRoutesRepo
|
routesRepo routesRepo.UIRoutesRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *MenuService {
|
func New() *MenuService {
|
||||||
return &MenuService{
|
return &MenuService{
|
||||||
categoriesRepo: categoriesRepo.New(),
|
categoryRepo: categoryrepo.New(),
|
||||||
routesRepo: routesRepo.New(),
|
routesRepo: routesRepo.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MenuService) GetCategoryTree(root_category_id uint, id_lang uint) (*model.Category, error) {
|
func (s *MenuService) GetCategoryTree(root_category_id uint, id_lang uint) (*model.Category, error) {
|
||||||
all_categories, err := s.categoriesRepo.GetAllCategories(id_lang)
|
all_categories, err := s.categoryRepo.RetrieveMenuCategories(id_lang)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &model.Category{}, err
|
return &model.Category{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove blacklisted categories
|
||||||
|
// to do so, we detach them from the main tree
|
||||||
|
for i := 0; i < len(all_categories); i++ {
|
||||||
|
if slices.Contains(constdata.CATEGORY_BLACKLIST, all_categories[i].CategoryID) {
|
||||||
|
all_categories[i].ParentID = all_categories[i].CategoryID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iso_code := all_categories[0].IsoCode
|
||||||
|
s.appendAdditional(&all_categories, id_lang, iso_code)
|
||||||
|
|
||||||
// find the root
|
// find the root
|
||||||
root_index := 0
|
root_index := 0
|
||||||
root_found := false
|
root_found := false
|
||||||
@@ -88,8 +102,8 @@ func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCate
|
|||||||
return node, true
|
return node, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MenuService) GetRoutes(id_lang uint) ([]model.Route, error) {
|
func (s *MenuService) GetRoutes(id_lang, roleId uint) ([]model.Route, error) {
|
||||||
return s.routesRepo.GetRoutes(id_lang)
|
return s.routesRepo.GetRoutes(id_lang, roleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) model.Category {
|
func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) model.Category {
|
||||||
@@ -98,7 +112,7 @@ func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) mod
|
|||||||
normal.CategoryID = scanned.CategoryID
|
normal.CategoryID = scanned.CategoryID
|
||||||
normal.Label = scanned.Name
|
normal.Label = scanned.Name
|
||||||
// normal.Active = scanned.Active == 1
|
// normal.Active = scanned.Active == 1
|
||||||
normal.Params = model.CategoryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode}
|
normal.Params = model.CategoryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode, Filter: scanned.Filter}
|
||||||
normal.Children = []model.Category{}
|
normal.Children = []model.Category{}
|
||||||
return normal
|
return normal
|
||||||
}
|
}
|
||||||
@@ -114,11 +128,14 @@ func (a ByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|||||||
func (a ByPosition) Less(i, j int) bool { return a[i].Position < a[j].Position }
|
func (a ByPosition) Less(i, j int) bool { return a[i].Position < a[j].Position }
|
||||||
|
|
||||||
func (s *MenuService) GetBreadcrumb(root_category_id uint, start_category_id uint, id_lang uint) ([]model.CategoryInBreadcrumb, error) {
|
func (s *MenuService) GetBreadcrumb(root_category_id uint, start_category_id uint, id_lang uint) ([]model.CategoryInBreadcrumb, error) {
|
||||||
all_categories, err := s.categoriesRepo.GetAllCategories(id_lang)
|
all_categories, err := s.categoryRepo.RetrieveMenuCategories(id_lang)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []model.CategoryInBreadcrumb{}, err
|
return []model.CategoryInBreadcrumb{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iso_code := all_categories[0].IsoCode
|
||||||
|
s.appendAdditional(&all_categories, id_lang, iso_code)
|
||||||
|
|
||||||
breadcrumb := []model.CategoryInBreadcrumb{}
|
breadcrumb := []model.CategoryInBreadcrumb{}
|
||||||
|
|
||||||
start_index := 0
|
start_index := 0
|
||||||
@@ -211,3 +228,24 @@ func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BTopM
|
|||||||
|
|
||||||
return roots, nil
|
return roots, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MenuService) appendAdditional(all_categories *[]model.ScannedCategory, id_lang uint, iso_code string) {
|
||||||
|
for i := 0; i < len(*all_categories); i++ {
|
||||||
|
(*all_categories)[i].Filter = "category_id_in=" + strconv.Itoa(int((*all_categories)[i].CategoryID))
|
||||||
|
}
|
||||||
|
|
||||||
|
var additional model.ScannedCategory
|
||||||
|
additional.CategoryID = 10001
|
||||||
|
additional.Name = "New Products"
|
||||||
|
additional.Active = 1
|
||||||
|
additional.Position = 10
|
||||||
|
additional.ParentID = 2
|
||||||
|
additional.IsRoot = 0
|
||||||
|
additional.LinkRewrite = i18n.T___(id_lang, "category.new_products")
|
||||||
|
additional.IsoCode = iso_code
|
||||||
|
|
||||||
|
additional.Visited = false
|
||||||
|
additional.Filter = "is_new_in=true"
|
||||||
|
|
||||||
|
*all_categories = append(*all_categories, additional)
|
||||||
|
}
|
||||||
|
|||||||
145
app/service/orderService/orderService.go
Normal file
145
app/service/orderService/orderService.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package orderService
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/repos/cartsRepo"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/repos/ordersRepo"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/service/addressesService"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/service/emailService"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderService struct {
|
||||||
|
ordersRepo ordersRepo.UIOrdersRepo
|
||||||
|
cartsRepo cartsRepo.UICartsRepo
|
||||||
|
addressesService *addressesService.AddressesService
|
||||||
|
emailService *emailService.EmailService
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *OrderService {
|
||||||
|
return &OrderService{
|
||||||
|
ordersRepo: ordersRepo.New(),
|
||||||
|
cartsRepo: cartsRepo.New(),
|
||||||
|
addressesService: addressesService.New(),
|
||||||
|
emailService: emailService.NewEmailService(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderService) Find(user *model.Customer, p find.Paging, filt *filters.FiltersList) (*find.Found[model.CustomerOrder], error) {
|
||||||
|
if !user.HasPermission(perms.OrdersViewAll) {
|
||||||
|
// append filter to view only this user's orders
|
||||||
|
idStr := strconv.FormatUint(uint64(user.ID), 10)
|
||||||
|
filt.Append(filters.Where("b2b_customer_orders.user_id = " + idStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := s.ordersRepo.Find(user.ID, p, filt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(list.Items); i++ {
|
||||||
|
address_unparsed, err := s.addressesService.ValidateAddressJson(list.Items[i].AddressString, list.Items[i].CountryID)
|
||||||
|
// log such errors
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("err: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Items[i].AddressUnparsed = &address_unparsed
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderService) PlaceNewOrder(user_id uint, cart_id uint, name string, country_id uint, address_info string) error {
|
||||||
|
_, err := s.addressesService.ValidateAddressJson(address_info, country_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := s.cartsRepo.UserHasCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return responseErrors.ErrUserHasNoSuchCart
|
||||||
|
}
|
||||||
|
|
||||||
|
cart, err := s.cartsRepo.RetrieveCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(cart.Products) == 0 {
|
||||||
|
return responseErrors.ErrEmptyCart
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" && cart.Name != nil {
|
||||||
|
name = *cart.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// all checks passed
|
||||||
|
err = s.ordersRepo.PlaceNewOrder(cart, name, country_id, address_info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// from this point onward we do not cancel this order.
|
||||||
|
|
||||||
|
// if no error is returned, remove the cart. This should be smooth
|
||||||
|
err = s.cartsRepo.RemoveCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
// Log error but don't fail placing order
|
||||||
|
_ = err
|
||||||
|
}
|
||||||
|
|
||||||
|
// send email to admin
|
||||||
|
go func(user_id uint) {
|
||||||
|
err := s.emailService.SendNewOrderPlacedNotification(user_id)
|
||||||
|
if err != nil {
|
||||||
|
// Log error but don't fail placing order
|
||||||
|
_ = err
|
||||||
|
}
|
||||||
|
}(user_id)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderService) ChangeOrderAddress(user *model.Customer, order_id uint, country_id uint, address_info string) error {
|
||||||
|
_, err := s.addressesService.ValidateAddressJson(address_info, country_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.HasPermission(perms.OrdersModifyAll) {
|
||||||
|
exists, err := s.ordersRepo.UserHasOrder(user.ID, order_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return responseErrors.ErrUserHasNoSuchOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.ordersRepo.ChangeOrderAddress(order_id, country_id, address_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is obiously just an initial version of this function
|
||||||
|
func (s *OrderService) ChangeOrderStatus(user *model.Customer, order_id uint, status string) error {
|
||||||
|
if !user.HasPermission(perms.OrdersModifyAll) {
|
||||||
|
exists, err := s.ordersRepo.UserHasOrder(user.ID, order_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return responseErrors.ErrUserHasNoSuchOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.ordersRepo.ChangeOrderStatus(order_id, status)
|
||||||
|
}
|
||||||
@@ -2,9 +2,15 @@ package productService
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/repos/productsRepo"
|
"git.ma-al.com/goc_daniel/b2b/app/repos/productsRepo"
|
||||||
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/query/filters"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/view"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProductService struct {
|
type ProductService struct {
|
||||||
@@ -17,11 +23,146 @@ func New() *ProductService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProductService) GetJSON(p_id_product, p_id_lang, p_id_customer, b2b_id_country, p_quantity int) (*json.RawMessage, error) {
|
func (s *ProductService) Get(
|
||||||
products, err := s.productsRepo.GetJSON(p_id_product, constdata.SHOP_ID, p_id_lang, p_id_customer, b2b_id_country, p_quantity)
|
p_id_product, p_id_lang, p_id_customer, b2b_id_country, p_quantity uint,
|
||||||
|
) (*json.RawMessage, error) {
|
||||||
|
|
||||||
|
product, err := s.productsRepo.GetBase(p_id_product, constdata.SHOP_ID, p_id_lang, p_id_customer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return products, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return products, nil
|
price, err := s.productsRepo.GetPrice(p_id_product, nil, constdata.SHOP_ID, p_id_customer, b2b_id_country, p_quantity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
variants, err := s.productsRepo.GetVariants(p_id_product, constdata.SHOP_ID, p_id_lang, p_id_customer, b2b_id_country, p_quantity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := view.ProductFull{
|
||||||
|
Product: product,
|
||||||
|
Price: price,
|
||||||
|
Variants: variants,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(variants) > 0 {
|
||||||
|
result.Variants = variants
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := json.RawMessage(jsonBytes)
|
||||||
|
return &raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProductService) Find(
|
||||||
|
idLang uint,
|
||||||
|
userID uint,
|
||||||
|
p find.Paging,
|
||||||
|
filters *filters.FiltersList,
|
||||||
|
customer *model.Customer,
|
||||||
|
quantity uint,
|
||||||
|
shopID uint,
|
||||||
|
) (*find.Found[model.ProductInList], error) {
|
||||||
|
|
||||||
|
if customer == nil || customer.Country == nil {
|
||||||
|
return nil, errors.New("customer is nil or missing fields")
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := s.productsRepo.Find(idLang, userID, p, filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. collect simple products (no variants)
|
||||||
|
simpleProductIndexes := make([]int, 0, len(found.Items))
|
||||||
|
|
||||||
|
for i := range found.Items {
|
||||||
|
if found.Items[i].VariantsNumber <= 0 {
|
||||||
|
simpleProductIndexes = append(simpleProductIndexes, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. resolve prices ONLY for simple products
|
||||||
|
for _, i := range simpleProductIndexes {
|
||||||
|
price, err := s.productsRepo.GetPrice(
|
||||||
|
found.Items[i].ProductID,
|
||||||
|
nil,
|
||||||
|
shopID,
|
||||||
|
customer.ID,
|
||||||
|
customer.CountryID,
|
||||||
|
quantity,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
found.Items[i].PriceTaxExcl = price.FinalTaxExcl
|
||||||
|
found.Items[i].PriceTaxIncl = price.FinalTaxIncl
|
||||||
|
}
|
||||||
|
|
||||||
|
return found, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProductService) GetProductAttributes(
|
||||||
|
langID uint,
|
||||||
|
productID uint,
|
||||||
|
shopID uint,
|
||||||
|
customerID uint,
|
||||||
|
countryID uint,
|
||||||
|
quantity uint,
|
||||||
|
) ([]view.ProductAttribute, error) {
|
||||||
|
variants, err := s.productsRepo.GetVariants(productID, constdata.SHOP_ID, langID, customerID, countryID, quantity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return variants, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProductService) AddToFavorites(userID uint, productID uint) error {
|
||||||
|
exists, err := s.productsRepo.ProductInDatabase(productID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return responseErrors.ErrProductNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err = s.productsRepo.ExistsInFavorites(userID, productID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return responseErrors.ErrAlreadyInFavorites
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.productsRepo.AddToFavorites(userID, productID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProductService) RemoveFromFavorites(userID uint, productID uint) error {
|
||||||
|
exists, err := s.productsRepo.ProductInDatabase(productID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return responseErrors.ErrProductNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err = s.productsRepo.ExistsInFavorites(userID, productID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return responseErrors.ErrNotInFavorites
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.productsRepo.RemoveFromFavorites(userID, productID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -90,19 +89,29 @@ func (s *ProductTranslationService) GetProductDescription(userID uint, productID
|
|||||||
// Updates relevant fields with the "updates" map
|
// Updates relevant fields with the "updates" map
|
||||||
func (s *ProductTranslationService) SaveProductDescription(userID uint, productID uint, productLangID uint, updates map[string]string) error {
|
func (s *ProductTranslationService) SaveProductDescription(userID uint, productID uint, productLangID uint, updates map[string]string) error {
|
||||||
// only some fields can be affected
|
// only some fields can be affected
|
||||||
allowedFields := []string{"description", "description_short", "meta_description", "meta_title", "name", "available_now", "available_later", "usage"}
|
allowedFields := []string{"description", "description_short", "link_rewrite", "meta_description", "meta_keywords", "meta_title", "name",
|
||||||
|
"available_now", "available_later", "delivery_in_stock", "delivery_out_stock", "usage"}
|
||||||
for key := range updates {
|
for key := range updates {
|
||||||
if !slices.Contains(allowedFields, key) {
|
if !slices.Contains(allowedFields, key) {
|
||||||
return responseErrors.ErrBadField
|
return responseErrors.ErrBadField
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if text, exists := updates["link_rewrite"]; exists {
|
||||||
|
// sanitize and check that link_rewrite is a valid url slug
|
||||||
|
sanitized := SanitizeSlug(text)
|
||||||
|
if !IsValidSlug(sanitized) {
|
||||||
|
return responseErrors.ErrInvalidURLSlug
|
||||||
|
}
|
||||||
|
|
||||||
|
updates["link_rewrite"] = sanitized
|
||||||
|
}
|
||||||
|
|
||||||
// check that fields description, description_short and usage, if they exist, have a valid html format
|
// check that fields description, description_short and usage, if they exist, have a valid html format
|
||||||
mustBeHTML := []string{"description", "description_short", "usage"}
|
mustBeHTML := []string{"description", "description_short", "usage"}
|
||||||
for i := 0; i < len(mustBeHTML); i++ {
|
for i := 0; i < len(mustBeHTML); i++ {
|
||||||
if _, exists := updates[mustBeHTML[i]]; exists {
|
if text, exists := updates[mustBeHTML[i]]; exists {
|
||||||
updates[mustBeHTML[i]] = parseAutoCloseTags(updates[mustBeHTML[i]])
|
if !isValidXHTML(text) {
|
||||||
if !isValidXHTML(updates[mustBeHTML[i]]) {
|
|
||||||
return responseErrors.ErrInvalidXHTML
|
return responseErrors.ErrInvalidXHTML
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,20 +147,28 @@ func (s *ProductTranslationService) TranslateProductDescription(userID uint, pro
|
|||||||
|
|
||||||
fields := []*string{&productDescription.Description,
|
fields := []*string{&productDescription.Description,
|
||||||
&productDescription.DescriptionShort,
|
&productDescription.DescriptionShort,
|
||||||
|
&productDescription.LinkRewrite,
|
||||||
&productDescription.MetaDescription,
|
&productDescription.MetaDescription,
|
||||||
|
&productDescription.MetaKeywords,
|
||||||
&productDescription.MetaTitle,
|
&productDescription.MetaTitle,
|
||||||
&productDescription.Name,
|
&productDescription.Name,
|
||||||
&productDescription.AvailableNow,
|
&productDescription.AvailableNow,
|
||||||
&productDescription.AvailableLater,
|
&productDescription.AvailableLater,
|
||||||
|
&productDescription.DeliveryInStock,
|
||||||
|
&productDescription.DeliveryOutStock,
|
||||||
&productDescription.Usage,
|
&productDescription.Usage,
|
||||||
}
|
}
|
||||||
keys := []string{"translation_of_product_description",
|
keys := []string{"translation_of_product_description",
|
||||||
"translation_of_product_short_description",
|
"translation_of_product_short_description",
|
||||||
|
"translation_of_product_url_link",
|
||||||
"translation_of_product_meta_description",
|
"translation_of_product_meta_description",
|
||||||
|
"translation_of_product_meta_keywords",
|
||||||
"translation_of_product_meta_title",
|
"translation_of_product_meta_title",
|
||||||
"translation_of_product_name",
|
"translation_of_product_name",
|
||||||
"translation_of_product_available_now",
|
"translation_of_product_available_now_message",
|
||||||
"translation_of_product_available_later",
|
"translation_of_product_available_later_message",
|
||||||
|
"translation_of_product_delivery_in_stock_message",
|
||||||
|
"translation_of_product_delivery_out_stock_message",
|
||||||
"translation_of_product_usage",
|
"translation_of_product_usage",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,17 +264,9 @@ func cleanForPrompt(s string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(xml.HTMLAutoClose, v.Name.Local) {
|
|
||||||
prompt += "/>"
|
|
||||||
} else {
|
|
||||||
prompt += ">"
|
prompt += ">"
|
||||||
}
|
|
||||||
|
|
||||||
case xml.EndElement:
|
case xml.EndElement:
|
||||||
if !slices.Contains(xml.HTMLAutoClose, v.Name.Local) {
|
|
||||||
prompt += "</" + attrName(v.Name) + ">"
|
prompt += "</" + attrName(v.Name) + ">"
|
||||||
}
|
|
||||||
|
|
||||||
case xml.CharData:
|
case xml.CharData:
|
||||||
prompt += string(v)
|
prompt += string(v)
|
||||||
case xml.Comment:
|
case xml.Comment:
|
||||||
@@ -298,43 +307,6 @@ func getStringInBetween(str string, start string, end string) (success bool, res
|
|||||||
return true, str[s : s+e]
|
return true, str[s : s+e]
|
||||||
}
|
}
|
||||||
|
|
||||||
// this converts input into HTML4 format.
|
|
||||||
// this really is ad-hoc solution, but it works.
|
|
||||||
func parseAutoCloseTags(s string) string {
|
|
||||||
alts := ""
|
|
||||||
for i, name := range xml.HTMLAutoClose {
|
|
||||||
if i > 0 {
|
|
||||||
alts += "|"
|
|
||||||
}
|
|
||||||
alts += name
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove closing </img> tags
|
|
||||||
reClose := regexp.MustCompile(`(?i)<\s*\/\s*(?:` + alts + `)\s*>`)
|
|
||||||
s = reClose.ReplaceAllString(s, "")
|
|
||||||
|
|
||||||
// convert <img ...> → <img ... />
|
|
||||||
// matches <img ...> that do NOT already end with />
|
|
||||||
reOpen := regexp.MustCompile(`(?i)<\s*(` + alts + `)\b([^>]*?)>`)
|
|
||||||
s = reOpen.ReplaceAllStringFunc(s, func(tag string) string {
|
|
||||||
trimmed := strings.TrimSpace(tag)
|
|
||||||
|
|
||||||
// Already self-closed: <img ... />, <br/>
|
|
||||||
if strings.HasSuffix(trimmed, "/>") {
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace final > with />
|
|
||||||
i := strings.LastIndex(tag, ">")
|
|
||||||
if i < 0 {
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
return tag[:i] + " />"
|
|
||||||
})
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// isValidXHTML checks if the string obeys the XHTML format
|
// isValidXHTML checks if the string obeys the XHTML format
|
||||||
func isValidXHTML(s string) bool {
|
func isValidXHTML(s string) bool {
|
||||||
r := strings.NewReader(s)
|
r := strings.NewReader(s)
|
||||||
@@ -410,12 +382,7 @@ func rebuildFromResponse(s_original string, s_response string) (bool, string) {
|
|||||||
result += fmt.Sprintf(` %s="%s"`, attrName(attr.Name), attr.Value)
|
result += fmt.Sprintf(` %s="%s"`, attrName(attr.Name), attr.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(xml.HTMLAutoClose, v_original.Name.Local) {
|
|
||||||
result += "/>"
|
|
||||||
} else {
|
|
||||||
result += ">"
|
result += ">"
|
||||||
}
|
|
||||||
|
|
||||||
case xml.CharData:
|
case xml.CharData:
|
||||||
result += string(v_response)
|
result += string(v_response)
|
||||||
@@ -433,7 +400,7 @@ func rebuildFromResponse(s_original string, s_response string) (bool, string) {
|
|||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if !slices.Contains(xml.HTMLAutoClose, v_original.Name.Local) {
|
if v_original.Name.Local != "img" {
|
||||||
result += "</" + attrName(v_original.Name) + ">"
|
result += "</" + attrName(v_original.Name) + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
68
app/service/productTranslationService/sanitizeURLSlug.go
Normal file
68
app/service/productTranslationService/sanitizeURLSlug.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package productTranslationService
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
|
"golang.org/x/text/runes"
|
||||||
|
"golang.org/x/text/transform"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsValidSlug(s string) bool {
|
||||||
|
var slug_regex2 = regexp2.MustCompile(constdata.SLUG_REGEX, regexp2.None)
|
||||||
|
|
||||||
|
ok, _ := slug_regex2.MatchString(s)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func SanitizeSlug(s string) string {
|
||||||
|
s = strings.TrimSpace(strings.ToLower(s))
|
||||||
|
|
||||||
|
// First apply explicit transliteration for language-specific letters.
|
||||||
|
s = transliterateSlug(s)
|
||||||
|
|
||||||
|
// Then normalize and strip any remaining combining marks.
|
||||||
|
s = removeDiacritics(s)
|
||||||
|
|
||||||
|
// Replace all non-alphanumeric runs with "-"
|
||||||
|
var non_alphanum_regex2 = regexp2.MustCompile(constdata.NON_ALNUM_REGEX, regexp2.None)
|
||||||
|
s, _ = non_alphanum_regex2.Replace(s, "-", -1, -1)
|
||||||
|
|
||||||
|
// Collapse repeated "-" and trim edges
|
||||||
|
var multi_dash_regex2 = regexp2.MustCompile(constdata.MULTI_DASH_REGEX, regexp2.None)
|
||||||
|
s, _ = multi_dash_regex2.Replace(s, "-", -1, -1)
|
||||||
|
|
||||||
|
s = strings.Trim(s, "-")
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func transliterateSlug(s string) string {
|
||||||
|
var cleared string
|
||||||
|
|
||||||
|
err := db.DB.Raw("SELECT slugify_eu(?)", s).Scan(&cleared).Error
|
||||||
|
if err != nil {
|
||||||
|
// log error
|
||||||
|
_ = err
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleared
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDiacritics(s string) string {
|
||||||
|
t := transform.Chain(
|
||||||
|
norm.NFD,
|
||||||
|
runes.Remove(runes.In(unicode.Mn)),
|
||||||
|
norm.NFC,
|
||||||
|
)
|
||||||
|
out, _, err := transform.String(t, s)
|
||||||
|
if err != nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
124
app/service/specificPriceService/specificPriceService.go
Normal file
124
app/service/specificPriceService/specificPriceService.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package specificPriceService
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/repos/specificPriceRepo"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SpecificPriceService struct {
|
||||||
|
specificPriceRepo specificPriceRepo.UISpecificPriceRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *SpecificPriceService {
|
||||||
|
return &SpecificPriceService{
|
||||||
|
specificPriceRepo: specificPriceRepo.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SpecificPriceService) Create(ctx context.Context, pr *model.SpecificPrice) (*model.SpecificPrice, error) {
|
||||||
|
if err := s.validateRequest(pr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.specificPriceRepo.Create(ctx, pr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SpecificPriceService) Update(ctx context.Context, id uint64, pr *model.SpecificPrice) (*model.SpecificPrice, error) {
|
||||||
|
existing, err := s.specificPriceRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if existing == nil {
|
||||||
|
return nil, responseErrors.ErrSpecificPriceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.validateUpdateRequest(pr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pr.ID = id
|
||||||
|
|
||||||
|
if err := s.specificPriceRepo.Update(ctx, pr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SpecificPriceService) GetByID(ctx context.Context, id uint64) (*model.SpecificPrice, error) {
|
||||||
|
pr, err := s.specificPriceRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if pr == nil {
|
||||||
|
return nil, responseErrors.ErrSpecificPriceNotFound
|
||||||
|
}
|
||||||
|
return pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SpecificPriceService) List(ctx context.Context) ([]*model.SpecificPrice, error) {
|
||||||
|
return s.specificPriceRepo.List(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SpecificPriceService) SetActive(ctx context.Context, id uint64, active bool) error {
|
||||||
|
pr, err := s.specificPriceRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pr == nil {
|
||||||
|
return responseErrors.ErrSpecificPriceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.specificPriceRepo.SetActive(ctx, id, active)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SpecificPriceService) Delete(ctx context.Context, id uint64) error {
|
||||||
|
pr, err := s.specificPriceRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pr == nil {
|
||||||
|
return responseErrors.ErrSpecificPriceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.specificPriceRepo.Delete(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SpecificPriceService) validateRequest(pr *model.SpecificPrice) error {
|
||||||
|
if pr.ReductionType != "amount" && pr.ReductionType != "percentage" {
|
||||||
|
return responseErrors.ErrInvalidReductionType
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr.ReductionType == "percentage" && pr.PercentageReduction == nil {
|
||||||
|
return responseErrors.ErrPercentageRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr.ReductionType == "amount" && pr.Price == nil {
|
||||||
|
return responseErrors.ErrPriceRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SpecificPriceService) validateUpdateRequest(pr *model.SpecificPrice) error {
|
||||||
|
if pr.ReductionType != "" && pr.ReductionType != "amount" && pr.ReductionType != "percentage" {
|
||||||
|
return responseErrors.ErrInvalidReductionType
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr.ReductionType == "percentage" && pr.PercentageReduction == nil {
|
||||||
|
return responseErrors.ErrPercentageRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr.ReductionType == "amount" && pr.Price == nil {
|
||||||
|
return responseErrors.ErrPriceRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/templ/emails/emailNewOrderPlacedNotification.templ
Normal file
26
app/templ/emails/emailNewOrderPlacedNotification.templ
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package emails
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/templ/layout"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/view"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
templ EmailNewOrderPlacedWrapper(data view.EmailLayout[view.EmailNewOrderPlacedData]) {
|
||||||
|
@layout.Base( i18n.T___(data.LangID, "email.email_new_order_placed_notification_title")) {
|
||||||
|
<div class="container">
|
||||||
|
<div class="email-wrapper">
|
||||||
|
<div class="email-header">
|
||||||
|
<h1>New Order Placed</h1>
|
||||||
|
</div>
|
||||||
|
<div class="email-body">
|
||||||
|
<p>Hello Administrator,</p>
|
||||||
|
<p>User with id { data.Data.UserID } has placed a new order. </p>
|
||||||
|
</div>
|
||||||
|
<div class="email-footer">
|
||||||
|
<p>© 2024 Gitea Manager. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,14 +3,35 @@ package constdata
|
|||||||
// PASSWORD_VALIDATION_REGEX is used by the frontend (JavaScript supports lookaheads).
|
// PASSWORD_VALIDATION_REGEX is used by the frontend (JavaScript supports lookaheads).
|
||||||
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 DEFAULT_PRODUCT_QUANTITY = 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
|
||||||
|
|
||||||
|
// since arrays can not be const
|
||||||
|
var CATEGORY_BLACKLIST = []uint{250}
|
||||||
|
|
||||||
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 MAX_AMOUNT_OF_PRODUCT_IN_CART = 1024
|
||||||
|
|
||||||
const USER_LOCALES_NAME = "user"
|
const MAX_AMOUNT_OF_ADDRESSES_PER_USER = 10
|
||||||
const USER_LOCALES_ID = "userID"
|
|
||||||
const LANG_LOCALES_ID = "langID"
|
const USER_LOCALE = "user"
|
||||||
|
|
||||||
|
// ORDERS
|
||||||
|
const NEW_ORDER_STATUS = "PENDING"
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
|
||||||
|
// Slug sanitization
|
||||||
|
const NON_ALNUM_REGEX = `[^a-z0-9]+`
|
||||||
|
const MULTI_DASH_REGEX = `-+`
|
||||||
|
const SLUG_REGEX = `^[a-z0-9]+(?:-[a-z0-9]+)*$`
|
||||||
|
|
||||||
|
const UNLOGGED_USER_ROLE_ID = 4
|
||||||
|
|||||||
@@ -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 GetCustomer(c fiber.Ctx) (*model.Customer, bool) {
|
||||||
|
user_locale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
|
||||||
|
if !ok || user_locale.User == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return user_locale.User, true
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package find
|
package find
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -28,18 +27,13 @@ type Found[T any] struct {
|
|||||||
Spec map[string]interface{} `json:"spec,omitempty"`
|
Spec map[string]interface{} `json:"spec,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wraps given query adding limit, offset clauses and SQL_CALC_FOUND_ROWS to it
|
|
||||||
// and running SELECT FOUND_ROWS() afterwards to fetch the total number
|
|
||||||
// (ignoring LIMIT) of results. The final results are wrapped into the
|
|
||||||
// [find.Found] type.
|
|
||||||
func Paginate[T any](langID uint, paging Paging, stmt *gorm.DB) (Found[T], error) {
|
func Paginate[T any](langID uint, paging Paging, stmt *gorm.DB) (Found[T], error) {
|
||||||
var items []T
|
var items []T
|
||||||
var count uint64
|
var count int64
|
||||||
|
|
||||||
// stmt.Debug()
|
stmt.Count(&count)
|
||||||
|
|
||||||
err := stmt.
|
err := stmt.
|
||||||
Clauses(SqlCalcFound()).
|
|
||||||
Offset(paging.Offset()).
|
Offset(paging.Offset()).
|
||||||
Limit(paging.Limit()).
|
Limit(paging.Limit()).
|
||||||
Find(&items).
|
Find(&items).
|
||||||
@@ -48,22 +42,14 @@ func Paginate[T any](langID uint, paging Paging, stmt *gorm.DB) (Found[T], error
|
|||||||
return Found[T]{}, err
|
return Found[T]{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
countInterface, ok := stmt.Get(FOUND_ROWS_CTX_KEY)
|
// columnsSpec := GetColumnsSpec[T](langID)
|
||||||
if !ok {
|
|
||||||
return Found[T]{}, errors.New(FOUND_ROWS_CTX_KEY + " value was not found in the gorm db context")
|
|
||||||
}
|
|
||||||
if count, ok = countInterface.(uint64); !ok {
|
|
||||||
return Found[T]{}, errors.New("failed to cast value under " + FOUND_ROWS_CTX_KEY + " to uint64")
|
|
||||||
}
|
|
||||||
|
|
||||||
columnsSpec := GetColumnsSpec[T](langID)
|
|
||||||
|
|
||||||
return Found[T]{
|
return Found[T]{
|
||||||
Items: items,
|
Items: items,
|
||||||
Count: uint(count),
|
Count: uint(count),
|
||||||
Spec: map[string]interface{}{
|
// Spec: map[string]interface{}{
|
||||||
"columns": columnsSpec,
|
// "columns": columnsSpec,
|
||||||
},
|
// },
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ var (
|
|||||||
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 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")
|
||||||
@@ -43,12 +44,16 @@ var (
|
|||||||
// Typed errors for product description handler
|
// Typed errors for product description handler
|
||||||
ErrBadAttribute = errors.New("bad or missing attribute value in header")
|
ErrBadAttribute = errors.New("bad or missing attribute value in header")
|
||||||
ErrBadField = errors.New("this field can not be updated")
|
ErrBadField = errors.New("this field can not be updated")
|
||||||
|
ErrInvalidURLSlug = errors.New("URL slug does not obey the industry standard")
|
||||||
ErrInvalidXHTML = errors.New("text is not in xhtml format")
|
ErrInvalidXHTML = errors.New("text is not in xhtml format")
|
||||||
ErrAIResponseFail = errors.New("AI responded with failure")
|
ErrAIResponseFail = errors.New("AI responded with failure")
|
||||||
ErrAIBadOutput = errors.New("AI response does not obey the format")
|
ErrAIBadOutput = errors.New("AI response does not obey the format")
|
||||||
|
|
||||||
// Typed errors for product list handler
|
// Typed errors for product handler
|
||||||
ErrBadPaging = errors.New("bad or missing paging attribute value in header")
|
ErrBadPaging = errors.New("bad or missing paging attribute value in header")
|
||||||
|
ErrProductNotFound = errors.New("product with provided id does not exist")
|
||||||
|
ErrAlreadyInFavorites = errors.New("the product already is in your favorites")
|
||||||
|
ErrNotInFavorites = errors.New("the product already is not in your favorites")
|
||||||
|
|
||||||
// Typed errors for menu handler
|
// Typed errors for menu handler
|
||||||
ErrNoRootFound = errors.New("no root found in categories table")
|
ErrNoRootFound = errors.New("no root found in categories table")
|
||||||
@@ -60,9 +65,34 @@ 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")
|
||||||
|
ErrAmountMustBePositive = errors.New("amount must be positive")
|
||||||
|
ErrAmountMustBeReasonable = errors.New("amount must be reasonable")
|
||||||
|
|
||||||
|
// Typed errors for orders handler
|
||||||
|
ErrEmptyCart = errors.New("the cart is empty")
|
||||||
|
ErrUserHasNoSuchOrder = errors.New("user does not have order with given id")
|
||||||
|
|
||||||
|
// Typed errors for price reduction handler
|
||||||
|
ErrInvalidReductionType = errors.New("invalid reduction type: must be 'amount' or 'percentage'")
|
||||||
|
ErrPercentageRequired = errors.New("percentage_reduction required when reduction_type is percentage")
|
||||||
|
ErrPriceRequired = errors.New("price required when reduction_type is amount")
|
||||||
|
ErrSpecificPriceNotFound = errors.New("price reduction not found")
|
||||||
|
|
||||||
|
// 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'")
|
||||||
|
|
||||||
// Typed errors for data parsing
|
// Typed errors for data parsing
|
||||||
ErrJSONBody = errors.New("invalid JSON body")
|
ErrJSONBody = errors.New("invalid JSON body")
|
||||||
|
|
||||||
|
// Typed errors for addresses
|
||||||
|
ErrMaxAmtOfAddressesReached = errors.New("maximal amount of addresses per user reached")
|
||||||
|
ErrUserHasNoSuchAddress = errors.New("user has no such address")
|
||||||
|
ErrInvalidCountryID = errors.New("invalid country id")
|
||||||
|
ErrInvalidAddressJSON = errors.New("invalid address json")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error represents an error with HTTP status code
|
// Error represents an error with HTTP status code
|
||||||
@@ -117,6 +147,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):
|
||||||
@@ -142,6 +174,8 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
return i18n.T_(c, "error.err_bad_attribute")
|
return i18n.T_(c, "error.err_bad_attribute")
|
||||||
case errors.Is(err, ErrBadField):
|
case errors.Is(err, ErrBadField):
|
||||||
return i18n.T_(c, "error.err_bad_field")
|
return i18n.T_(c, "error.err_bad_field")
|
||||||
|
case errors.Is(err, ErrInvalidURLSlug):
|
||||||
|
return i18n.T_(c, "error.err_invalid_url_slug")
|
||||||
case errors.Is(err, ErrInvalidXHTML):
|
case errors.Is(err, ErrInvalidXHTML):
|
||||||
return i18n.T_(c, "error.err_invalid_html")
|
return i18n.T_(c, "error.err_invalid_html")
|
||||||
case errors.Is(err, ErrAIResponseFail):
|
case errors.Is(err, ErrAIResponseFail):
|
||||||
@@ -151,26 +185,70 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
|
|
||||||
case errors.Is(err, ErrBadPaging):
|
case errors.Is(err, ErrBadPaging):
|
||||||
return i18n.T_(c, "error.err_bad_paging")
|
return i18n.T_(c, "error.err_bad_paging")
|
||||||
|
case errors.Is(err, ErrProductNotFound):
|
||||||
|
return i18n.T_(c, "error.err_product_not_found")
|
||||||
|
case errors.Is(err, ErrAlreadyInFavorites):
|
||||||
|
return i18n.T_(c, "error.err_already_in_favorites")
|
||||||
|
case errors.Is(err, ErrNotInFavorites):
|
||||||
|
return i18n.T_(c, "error.err_already_not_in_favorites")
|
||||||
|
|
||||||
case errors.Is(err, ErrNoRootFound):
|
case errors.Is(err, ErrNoRootFound):
|
||||||
return i18n.T_(c, "error.no_root_found")
|
return i18n.T_(c, "error.err_no_root_found")
|
||||||
case errors.Is(err, ErrCircularDependency):
|
case errors.Is(err, ErrCircularDependency):
|
||||||
return i18n.T_(c, "error.circular_dependency")
|
return i18n.T_(c, "error.err_circular_dependency")
|
||||||
case errors.Is(err, ErrStartCategoryNotFound):
|
case errors.Is(err, ErrStartCategoryNotFound):
|
||||||
return i18n.T_(c, "error.start_category_not_found")
|
return i18n.T_(c, "error.err_start_category_not_found")
|
||||||
case errors.Is(err, ErrRootNeverReached):
|
case errors.Is(err, ErrRootNeverReached):
|
||||||
return i18n.T_(c, "error.root_never_reached")
|
return i18n.T_(c, "error.err_root_never_reached")
|
||||||
|
|
||||||
case errors.Is(err, ErrMaxAmtOfCartsReached):
|
case errors.Is(err, ErrMaxAmtOfCartsReached):
|
||||||
return i18n.T_(c, "error.max_amt_of_carts_reached")
|
return i18n.T_(c, "error.err_max_amt_of_carts_reached")
|
||||||
case errors.Is(err, ErrUserHasNoSuchCart):
|
case errors.Is(err, ErrUserHasNoSuchCart):
|
||||||
return i18n.T_(c, "error.user_has_no_such_cart")
|
return i18n.T_(c, "error.err_user_has_no_such_cart")
|
||||||
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.err_product_or_its_variation_does_not_exist")
|
||||||
|
case errors.Is(err, ErrAmountMustBePositive):
|
||||||
|
return i18n.T_(c, "error.err_amount_must_be_positive")
|
||||||
|
case errors.Is(err, ErrAmountMustBeReasonable):
|
||||||
|
return i18n.T_(c, "error.err_amount_must_be_reasonable")
|
||||||
|
|
||||||
|
case errors.Is(err, ErrEmptyCart):
|
||||||
|
return i18n.T_(c, "error.err_cart_is_empty")
|
||||||
|
case errors.Is(err, ErrUserHasNoSuchOrder):
|
||||||
|
return i18n.T_(c, "error.err_user_has_no_such_order")
|
||||||
|
|
||||||
|
case errors.Is(err, ErrAccessDenied):
|
||||||
|
return i18n.T_(c, "error.err_access_denied")
|
||||||
|
case errors.Is(err, ErrFolderDoesNotExist):
|
||||||
|
return i18n.T_(c, "error.err_folder_does_not_exist")
|
||||||
|
case errors.Is(err, ErrFileDoesNotExist):
|
||||||
|
return i18n.T_(c, "error.err_file_does_not_exist")
|
||||||
|
case errors.Is(err, ErrNameTaken):
|
||||||
|
return i18n.T_(c, "error.err_name_taken")
|
||||||
|
case errors.Is(err, ErrMissingFileFieldDocument):
|
||||||
|
return i18n.T_(c, "error.err_missing_file_field_document")
|
||||||
|
|
||||||
|
case errors.Is(err, ErrInvalidReductionType):
|
||||||
|
return i18n.T_(c, "error.invalid_reduction_type")
|
||||||
|
case errors.Is(err, ErrPercentageRequired):
|
||||||
|
return i18n.T_(c, "error.percentage_required")
|
||||||
|
case errors.Is(err, ErrPriceRequired):
|
||||||
|
return i18n.T_(c, "error.price_required")
|
||||||
|
case errors.Is(err, ErrSpecificPriceNotFound):
|
||||||
|
return i18n.T_(c, "error.price_reduction_not_found")
|
||||||
|
|
||||||
case errors.Is(err, ErrJSONBody):
|
case errors.Is(err, ErrJSONBody):
|
||||||
return i18n.T_(c, "error.err_json_body")
|
return i18n.T_(c, "error.err_json_body")
|
||||||
|
|
||||||
|
case errors.Is(err, ErrMaxAmtOfAddressesReached):
|
||||||
|
return i18n.T_(c, "error.err_max_amt_of_addresses_reached")
|
||||||
|
case errors.Is(err, ErrUserHasNoSuchAddress):
|
||||||
|
return i18n.T_(c, "error.err_user_has_no_such_address")
|
||||||
|
case errors.Is(err, ErrInvalidCountryID):
|
||||||
|
return i18n.T_(c, "error.err_invalid_country_id")
|
||||||
|
case errors.Is(err, ErrInvalidAddressJSON):
|
||||||
|
return i18n.T_(c, "error.err_invalid_address_json")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return i18n.T_(c, "error.err_internal_server_error")
|
return i18n.T_(c, "error.err_internal_server_error")
|
||||||
}
|
}
|
||||||
@@ -195,6 +273,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),
|
||||||
@@ -206,8 +285,12 @@ func GetErrorStatus(err error) int {
|
|||||||
errors.Is(err, ErrInvalidPassword),
|
errors.Is(err, ErrInvalidPassword),
|
||||||
errors.Is(err, ErrBadAttribute),
|
errors.Is(err, ErrBadAttribute),
|
||||||
errors.Is(err, ErrBadField),
|
errors.Is(err, ErrBadField),
|
||||||
|
errors.Is(err, ErrInvalidURLSlug),
|
||||||
errors.Is(err, ErrInvalidXHTML),
|
errors.Is(err, ErrInvalidXHTML),
|
||||||
errors.Is(err, ErrBadPaging),
|
errors.Is(err, ErrBadPaging),
|
||||||
|
errors.Is(err, ErrProductNotFound),
|
||||||
|
errors.Is(err, ErrAlreadyInFavorites),
|
||||||
|
errors.Is(err, ErrNotInFavorites),
|
||||||
errors.Is(err, ErrNoRootFound),
|
errors.Is(err, ErrNoRootFound),
|
||||||
errors.Is(err, ErrCircularDependency),
|
errors.Is(err, ErrCircularDependency),
|
||||||
errors.Is(err, ErrStartCategoryNotFound),
|
errors.Is(err, ErrStartCategoryNotFound),
|
||||||
@@ -215,8 +298,26 @@ func GetErrorStatus(err error) int {
|
|||||||
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, ErrJSONBody):
|
errors.Is(err, ErrAmountMustBePositive),
|
||||||
|
errors.Is(err, ErrAmountMustBeReasonable),
|
||||||
|
errors.Is(err, ErrEmptyCart),
|
||||||
|
errors.Is(err, ErrUserHasNoSuchOrder),
|
||||||
|
errors.Is(err, ErrInvalidReductionType),
|
||||||
|
errors.Is(err, ErrPercentageRequired),
|
||||||
|
errors.Is(err, ErrPriceRequired),
|
||||||
|
errors.Is(err, ErrAccessDenied),
|
||||||
|
errors.Is(err, ErrFolderDoesNotExist),
|
||||||
|
errors.Is(err, ErrFileDoesNotExist),
|
||||||
|
errors.Is(err, ErrNameTaken),
|
||||||
|
errors.Is(err, ErrMissingFileFieldDocument),
|
||||||
|
errors.Is(err, ErrJSONBody),
|
||||||
|
errors.Is(err, ErrMaxAmtOfAddressesReached),
|
||||||
|
errors.Is(err, ErrUserHasNoSuchAddress),
|
||||||
|
errors.Is(err, ErrInvalidCountryID),
|
||||||
|
errors.Is(err, ErrInvalidAddressJSON):
|
||||||
return fiber.StatusBadRequest
|
return fiber.StatusBadRequest
|
||||||
|
case errors.Is(err, ErrSpecificPriceNotFound):
|
||||||
|
return fiber.StatusNotFound
|
||||||
case errors.Is(err, ErrEmailExists):
|
case errors.Is(err, ErrEmailExists):
|
||||||
return fiber.StatusConflict
|
return fiber.StatusConflict
|
||||||
case errors.Is(err, ErrAIResponseFail),
|
case errors.Is(err, ErrAIResponseFail),
|
||||||
|
|||||||
@@ -18,3 +18,7 @@ type EmailAdminNotificationData struct {
|
|||||||
type EmailPasswordResetData struct {
|
type EmailPasswordResetData struct {
|
||||||
ResetURL string
|
ResetURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EmailNewOrderPlacedData struct {
|
||||||
|
UserID uint
|
||||||
|
}
|
||||||
|
|||||||
98
app/view/product.go
Normal file
98
app/view/product.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProductAttribute struct {
|
||||||
|
IDProductAttribute uint `gorm:"column:id_product_attribute" json:"id_product_attribute"`
|
||||||
|
Reference string `gorm:"column:reference" json:"reference"`
|
||||||
|
BasePrice float64 `gorm:"column:base_price" json:"base_price"`
|
||||||
|
PriceTaxExcl float64 `gorm:"column:price_tax_excl" json:"price_tax_excl"`
|
||||||
|
PriceTaxIncl float64 `gorm:"column:price_tax_incl" json:"price_tax_incl"`
|
||||||
|
Quantity int64 `gorm:"column:quantity" json:"quantity"`
|
||||||
|
Attributes json.RawMessage `gorm:"column:attributes" json:"attributes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Price struct {
|
||||||
|
Base float64 `json:"base"`
|
||||||
|
FinalTaxExcl float64 `json:"final_tax_excl"`
|
||||||
|
FinalTaxIncl float64 `json:"final_tax_incl"`
|
||||||
|
TaxRate float64 `json:"tax_rate"`
|
||||||
|
Priority int `json:"priority"` // or string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Variant struct {
|
||||||
|
ID uint `json:"id_product_attribute"`
|
||||||
|
Reference string `json:"reference"`
|
||||||
|
|
||||||
|
BasePrice float64 `json:"base_price"`
|
||||||
|
FinalExcl float64 `json:"final_tax_excl"`
|
||||||
|
FinalIncl float64 `json:"final_tax_incl"`
|
||||||
|
|
||||||
|
Stock int `json:"stock"`
|
||||||
|
}
|
||||||
|
type ProductFull struct {
|
||||||
|
Product Product `json:"product"`
|
||||||
|
Price Price `json:"price"`
|
||||||
|
Variants []ProductAttribute `json:"variants,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Product struct {
|
||||||
|
ID uint `gorm:"column:id" json:"id"`
|
||||||
|
Reference string `gorm:"column:reference" json:"reference"`
|
||||||
|
SupplierReference string `gorm:"column:supplier_reference" json:"supplier_reference,omitempty"`
|
||||||
|
EAN13 string `gorm:"column:ean13" json:"ean13,omitempty"`
|
||||||
|
UPC string `gorm:"column:upc" json:"upc,omitempty"`
|
||||||
|
ISBN string `gorm:"column:isbn" json:"isbn,omitempty"`
|
||||||
|
|
||||||
|
// Basic Price (from product table)
|
||||||
|
BasePrice float64 `gorm:"column:base_price" json:"base_price"`
|
||||||
|
WholesalePrice float64 `gorm:"column:wholesale_price" json:"wholesale_price,omitempty"`
|
||||||
|
Unity string `gorm:"column:unity" json:"unity,omitempty"`
|
||||||
|
UnitPriceRatio float64 `gorm:"column:unit_price_ratio" json:"unit_price_ratio,omitempty"`
|
||||||
|
|
||||||
|
// Stock & Availability
|
||||||
|
Quantity int `gorm:"column:quantity" json:"quantity"`
|
||||||
|
MinimalQuantity int `gorm:"column:minimal_quantity" json:"minimal_quantity"`
|
||||||
|
AvailableForOrder bool `gorm:"column:available_for_order" json:"available_for_order"`
|
||||||
|
AvailableDate string `gorm:"column:available_date" json:"available_date,omitempty"`
|
||||||
|
OutOfStockBehavior int `gorm:"column:out_of_stock_behavior" json:"out_of_stock_behavior"`
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
OnSale bool `gorm:"column:on_sale" json:"on_sale"`
|
||||||
|
ShowPrice bool `gorm:"column:show_price" json:"show_price"`
|
||||||
|
Condition string `gorm:"column:condition" json:"condition"`
|
||||||
|
IsVirtual bool `gorm:"column:is_virtual" json:"is_virtual"`
|
||||||
|
|
||||||
|
// Physical
|
||||||
|
Weight float64 `gorm:"column:weight" json:"weight"`
|
||||||
|
Width float64 `gorm:"column:width" json:"width"`
|
||||||
|
Height float64 `gorm:"column:height" json:"height"`
|
||||||
|
Depth float64 `gorm:"column:depth" json:"depth"`
|
||||||
|
AdditionalShippingCost float64 `gorm:"column:additional_shipping_cost" json:"additional_shipping_cost,omitempty"`
|
||||||
|
|
||||||
|
// Delivery
|
||||||
|
DeliveryDays int `gorm:"column:delivery_days" json:"delivery_days,omitempty"`
|
||||||
|
|
||||||
|
// Status
|
||||||
|
Active bool `gorm:"column:active" json:"active"`
|
||||||
|
Visibility string `gorm:"column:visibility" json:"visibility"`
|
||||||
|
Indexed bool `gorm:"column:indexed" json:"indexed"`
|
||||||
|
|
||||||
|
// Timestamps
|
||||||
|
DateAdd time.Time `gorm:"column:date_add" json:"date_add"`
|
||||||
|
DateUpd time.Time `gorm:"column:date_upd" json:"date_upd"`
|
||||||
|
|
||||||
|
// Language fields
|
||||||
|
Name string `gorm:"column:name" json:"name"`
|
||||||
|
Description string `gorm:"column:description" json:"description"`
|
||||||
|
DescriptionShort string `gorm:"column:description_short" json:"description_short"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Manufacturer string `gorm:"column:manufacturer" json:"manufacturer"`
|
||||||
|
Category string `gorm:"column:category" json:"category"`
|
||||||
|
|
||||||
|
IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"`
|
||||||
|
}
|
||||||
55
bo/auto-imports.d.ts
vendored
55
bo/auto-imports.d.ts
vendored
@@ -7,10 +7,10 @@
|
|||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
const avatarGroupInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useAvatarGroup.js').avatarGroupInjectionKey
|
const avatarGroupInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useAvatarGroup.js').avatarGroupInjectionKey
|
||||||
const defineLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineLocale').defineLocale
|
const defineLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineLocale.js').defineLocale
|
||||||
const defineShortcuts: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts').defineShortcuts
|
const defineShortcuts: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts.js').defineShortcuts
|
||||||
const extendLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineLocale').extendLocale
|
const extendLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineLocale.js').extendLocale
|
||||||
const extractShortcuts: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts').extractShortcuts
|
const extractShortcuts: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts.js').extractShortcuts
|
||||||
const fieldGroupInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFieldGroup.js').fieldGroupInjectionKey
|
const fieldGroupInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFieldGroup.js').fieldGroupInjectionKey
|
||||||
const formBusInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formBusInjectionKey
|
const formBusInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formBusInjectionKey
|
||||||
const formErrorsInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formErrorsInjectionKey
|
const formErrorsInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formErrorsInjectionKey
|
||||||
@@ -29,17 +29,46 @@ declare global {
|
|||||||
const useAvatarGroup: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useAvatarGroup.js').useAvatarGroup
|
const useAvatarGroup: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useAvatarGroup.js').useAvatarGroup
|
||||||
const useComponentIcons: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentIcons.js').useComponentIcons
|
const useComponentIcons: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentIcons.js').useComponentIcons
|
||||||
const useComponentUI: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentUI.js').useComponentUI
|
const useComponentUI: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentUI.js').useComponentUI
|
||||||
const useContentSearch: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useContentSearch').useContentSearch
|
const useContentSearch: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useContentSearch.js').useContentSearch
|
||||||
const useEditorMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useEditorMenu.js').useEditorMenu
|
const useEditorMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useEditorMenu.js').useEditorMenu
|
||||||
const useFieldGroup: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFieldGroup.js').useFieldGroup
|
const useFieldGroup: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFieldGroup.js').useFieldGroup
|
||||||
const useFileUpload: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFileUpload').useFileUpload
|
const useFileUpload: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFileUpload.js').useFileUpload
|
||||||
const useFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField').useFormField
|
const useFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').useFormField
|
||||||
const useKbd: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useKbd').useKbd
|
const useKbd: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useKbd.js').useKbd
|
||||||
const useLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useLocale.js').useLocale
|
const useLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useLocale.js').useLocale
|
||||||
const useOverlay: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useOverlay').useOverlay
|
const useOverlay: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useOverlay.js').useOverlay
|
||||||
const usePortal: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/usePortal.js').usePortal
|
const usePortal: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/usePortal.js').usePortal
|
||||||
const useResizable: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useResizable').useResizable
|
const useResizable: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useResizable.js').useResizable
|
||||||
const useScrollShadow: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useScrollShadow').useScrollShadow
|
const useScrollspy: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useScrollspy.js').useScrollspy
|
||||||
const useScrollspy: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useScrollspy').useScrollspy
|
const useToast: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useToast.js').useToast
|
||||||
const useToast: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useToast').useToast
|
}
|
||||||
|
// for type re-export
|
||||||
|
declare global {
|
||||||
|
// @ts-ignore
|
||||||
|
export type { ShortcutConfig, ShortcutsConfig, ShortcutsOptions } from './node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts.d'
|
||||||
|
import('./node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts.d')
|
||||||
|
// @ts-ignore
|
||||||
|
export type { UseComponentIconsProps } from './node_modules/@nuxt/ui/dist/runtime/composables/useComponentIcons.d'
|
||||||
|
import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentIcons.d')
|
||||||
|
// @ts-ignore
|
||||||
|
export type { ThemeUI, ThemeRootContext } from './node_modules/@nuxt/ui/dist/runtime/composables/useComponentUI.d'
|
||||||
|
import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentUI.d')
|
||||||
|
// @ts-ignore
|
||||||
|
export type { EditorMenuOptions } from './node_modules/@nuxt/ui/dist/runtime/composables/useEditorMenu.d'
|
||||||
|
import('./node_modules/@nuxt/ui/dist/runtime/composables/useEditorMenu.d')
|
||||||
|
// @ts-ignore
|
||||||
|
export type { UseFileUploadOptions } from './node_modules/@nuxt/ui/dist/runtime/composables/useFileUpload.d'
|
||||||
|
import('./node_modules/@nuxt/ui/dist/runtime/composables/useFileUpload.d')
|
||||||
|
// @ts-ignore
|
||||||
|
export type { KbdKey, KbdKeySpecific } from './node_modules/@nuxt/ui/dist/runtime/composables/useKbd.d'
|
||||||
|
import('./node_modules/@nuxt/ui/dist/runtime/composables/useKbd.d')
|
||||||
|
// @ts-ignore
|
||||||
|
export type { OverlayOptions, Overlay } from './node_modules/@nuxt/ui/dist/runtime/composables/useOverlay.d'
|
||||||
|
import('./node_modules/@nuxt/ui/dist/runtime/composables/useOverlay.d')
|
||||||
|
// @ts-ignore
|
||||||
|
export type { UseResizableProps, UseResizableReturn } from './node_modules/@nuxt/ui/dist/runtime/composables/useResizable.d'
|
||||||
|
import('./node_modules/@nuxt/ui/dist/runtime/composables/useResizable.d')
|
||||||
|
// @ts-ignore
|
||||||
|
export type { Toast } from './node_modules/@nuxt/ui/dist/runtime/composables/useToast.d'
|
||||||
|
import('./node_modules/@nuxt/ui/dist/runtime/composables/useToast.d')
|
||||||
}
|
}
|
||||||
|
|||||||
392
bo/bun.lock
392
bo/bun.lock
@@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "bo",
|
"name": "bo",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "^4.6.0",
|
"@nuxt/ui": "^4.5.0",
|
||||||
"@tailwindcss/vite": "^4.2.2",
|
"@tailwindcss/vite": "^4.2.0",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"reka-ui": "^2.9.3",
|
"tailwindcss": "^4.2.0",
|
||||||
"tailwindcss": "^4.2.2",
|
|
||||||
"vue": "beta",
|
"vue": "beta",
|
||||||
"vue-chartjs": "^5.3.3",
|
"vue-chartjs": "^5.3.3",
|
||||||
"vue-i18n": "^11.3.0",
|
"vue-i18n": "11",
|
||||||
"vue-router": "^5.0.4",
|
"vue-router": "^5.0.2",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node24": "^24.0.4",
|
"@tsconfig/node24": "^24.0.4",
|
||||||
"@types/node": "^24.12.0",
|
"@types/node": "^24.10.13",
|
||||||
"@vitejs/plugin-vue": "^6.0.5",
|
"@vitejs/plugin-vue": "^6.0.4",
|
||||||
"@vue/eslint-config-typescript": "^14.7.0",
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
"@vue/tsconfig": "^0.8.1",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
"prettier": "3.8.1",
|
"prettier": "3.8.1",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "beta",
|
"vite": "beta",
|
||||||
"vite-plugin-vue-devtools": "^8.1.1",
|
"vite-plugin-vue-devtools": "^8.0.6",
|
||||||
"vue-tsc": "^3.2.6",
|
"vue-tsc": "^3.2.4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -88,9 +88,9 @@
|
|||||||
|
|
||||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||||
|
|
||||||
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
||||||
|
|
||||||
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
|
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||||
|
|
||||||
"@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-decorators": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA=="],
|
"@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-decorators": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA=="],
|
||||||
|
|
||||||
@@ -114,63 +114,63 @@
|
|||||||
|
|
||||||
"@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="],
|
"@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="],
|
||||||
|
|
||||||
"@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
|
"@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||||
|
|
||||||
"@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
|
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||||
|
|
||||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
|
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||||
|
|
||||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
|
||||||
|
|
||||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="],
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
|
||||||
|
|
||||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="],
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
|
||||||
|
|
||||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="],
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
|
||||||
|
|
||||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="],
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
|
||||||
|
|
||||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="],
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="],
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
|
||||||
|
|
||||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="],
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
|
||||||
|
|
||||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="],
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
|
||||||
|
|
||||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="],
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
|
||||||
|
|
||||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="],
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
|
||||||
|
|
||||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="],
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
|
||||||
|
|
||||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="],
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
|
||||||
|
|
||||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="],
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
|
||||||
|
|
||||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="],
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
|
||||||
|
|
||||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="],
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
|
||||||
|
|
||||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="],
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
|
||||||
|
|
||||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="],
|
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
|
||||||
|
|
||||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="],
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
|
||||||
|
|
||||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="],
|
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
|
||||||
|
|
||||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="],
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
|
||||||
|
|
||||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="],
|
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
|
||||||
|
|
||||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="],
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
|
||||||
|
|
||||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="],
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
|
||||||
|
|
||||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="],
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
|
||||||
|
|
||||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="],
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
|
||||||
|
|
||||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@
|
|||||||
|
|
||||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||||
|
|
||||||
"@iconify/collections": ["@iconify/collections@1.0.668", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-d4ygrDoTqb02p7YNHdNzy64y4VQG0bd3w26EH+mJ0eKAj+DSWxDJ1Qw/WdCAqhKfjb+mRp1n9+YwcELs2YWVlg=="],
|
"@iconify/collections": ["@iconify/collections@1.0.659", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-gD9i7zfQpr3ZAv1BywaEri8o008KQT9iYaMw5EY4Y/y0qeEZB12FJMMyJGS3NvSeG80udu4kx8aZZLRsAdcANw=="],
|
||||||
|
|
||||||
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@
|
|||||||
|
|
||||||
"@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="],
|
"@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="],
|
||||||
|
|
||||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||||
|
|
||||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||||
|
|
||||||
@@ -242,17 +242,17 @@
|
|||||||
|
|
||||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||||
|
|
||||||
"@nuxt/devtools-kit": ["@nuxt/devtools-kit@3.2.4", "", { "dependencies": { "@nuxt/kit": "^4.4.2", "execa": "^8.0.1" }, "peerDependencies": { "vite": ">=6.0" } }, "sha512-Yxy2Xgmq5hf3dQy983V0xh0OJV2mYwRZz9eVIGc3EaribdFGPDNGMMbYqX9qCty3Pbxn/bCF3J0UyPaNlHVayQ=="],
|
"@nuxt/devtools-kit": ["@nuxt/devtools-kit@3.2.3", "", { "dependencies": { "@nuxt/kit": "^4.3.1", "execa": "^8.0.1" }, "peerDependencies": { "vite": ">=6.0" } }, "sha512-5zj7Xx5CDI6P84kMalXoxGLd470buF6ncsRhiEPq8UlwdpVeR7bwi8QnparZNFBdG79bZ5KUkfi5YDXpLYPoIA=="],
|
||||||
|
|
||||||
"@nuxt/fonts": ["@nuxt/fonts@0.14.0", "", { "dependencies": { "@nuxt/devtools-kit": "^3.2.1", "@nuxt/kit": "^4.2.2", "consola": "^3.4.2", "defu": "^6.1.4", "fontless": "^0.2.1", "h3": "^1.15.5", "magic-regexp": "^0.10.0", "ofetch": "^1.5.1", "pathe": "^2.0.3", "sirv": "^3.0.2", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unifont": "^0.7.4", "unplugin": "^3.0.0", "unstorage": "^1.17.4" } }, "sha512-4uXQl9fa5F4ibdgU8zomoOcyMdnwgdem+Pi8JEqeDYI5yPR32Kam6HnuRr47dTb97CstaepAvXPWQUUHMtjsFQ=="],
|
"@nuxt/fonts": ["@nuxt/fonts@0.14.0", "", { "dependencies": { "@nuxt/devtools-kit": "^3.2.1", "@nuxt/kit": "^4.2.2", "consola": "^3.4.2", "defu": "^6.1.4", "fontless": "^0.2.1", "h3": "^1.15.5", "magic-regexp": "^0.10.0", "ofetch": "^1.5.1", "pathe": "^2.0.3", "sirv": "^3.0.2", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unifont": "^0.7.4", "unplugin": "^3.0.0", "unstorage": "^1.17.4" } }, "sha512-4uXQl9fa5F4ibdgU8zomoOcyMdnwgdem+Pi8JEqeDYI5yPR32Kam6HnuRr47dTb97CstaepAvXPWQUUHMtjsFQ=="],
|
||||||
|
|
||||||
"@nuxt/icon": ["@nuxt/icon@2.2.1", "", { "dependencies": { "@iconify/collections": "^1.0.641", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.0", "@iconify/vue": "^5.0.0", "@nuxt/devtools-kit": "^3.1.1", "@nuxt/kit": "^4.2.2", "consola": "^3.4.2", "local-pkg": "^1.1.2", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinyglobby": "^0.2.15" } }, "sha512-GI840yYGuvHI0BGDQ63d6rAxGzG96jQcWrnaWIQKlyQo/7sx9PjXkSHckXUXyX1MCr9zY6U25Td6OatfY6Hklw=="],
|
"@nuxt/icon": ["@nuxt/icon@2.2.1", "", { "dependencies": { "@iconify/collections": "^1.0.641", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.0", "@iconify/vue": "^5.0.0", "@nuxt/devtools-kit": "^3.1.1", "@nuxt/kit": "^4.2.2", "consola": "^3.4.2", "local-pkg": "^1.1.2", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinyglobby": "^0.2.15" } }, "sha512-GI840yYGuvHI0BGDQ63d6rAxGzG96jQcWrnaWIQKlyQo/7sx9PjXkSHckXUXyX1MCr9zY6U25Td6OatfY6Hklw=="],
|
||||||
|
|
||||||
"@nuxt/kit": ["@nuxt/kit@4.4.2", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "mlly": "^1.8.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-5+IPRNX2CjkBhuWUwz0hBuLqiaJPRoKzQ+SvcdrQDbAyE+VDeFt74VpSFr5/R0ujrK4b+XnSHUJWdS72w6hsog=="],
|
"@nuxt/kit": ["@nuxt/kit@4.3.1", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-UjBFt72dnpc+83BV3OIbCT0YHLevJtgJCHpxMX0YRKWLDhhbcDdUse87GtsQBrjvOzK7WUNUYLDS/hQLYev5rA=="],
|
||||||
|
|
||||||
"@nuxt/schema": ["@nuxt/schema@4.4.2", "", { "dependencies": { "@vue/shared": "^3.5.30", "defu": "^6.1.4", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "std-env": "^4.0.0" } }, "sha512-/q6C7Qhiricgi+PKR7ovBnJlKTL0memCbA1CzRT+itCW/oeYzUfeMdQ35mGntlBoyRPNrMXbzuSUhfDbSCU57w=="],
|
"@nuxt/schema": ["@nuxt/schema@4.3.1", "", { "dependencies": { "@vue/shared": "^3.5.27", "defu": "^6.1.4", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "std-env": "^3.10.0" } }, "sha512-S+wHJdYDuyk9I43Ej27y5BeWMZgi7R/UVql3b3qtT35d0fbpXW7fUenzhLRCCDC6O10sjguc6fcMcR9sMKvV8g=="],
|
||||||
|
|
||||||
"@nuxt/ui": ["@nuxt/ui@4.6.0", "", { "dependencies": { "@floating-ui/dom": "^1.7.6", "@iconify/vue": "^5.0.0", "@internationalized/date": "^3.12.0", "@internationalized/number": "^3.6.5", "@nuxt/fonts": "^0.14.0", "@nuxt/icon": "^2.2.1", "@nuxt/kit": "^4.4.2", "@nuxt/schema": "^4.4.2", "@nuxtjs/color-mode": "^3.5.2", "@standard-schema/spec": "^1.1.0", "@tailwindcss/postcss": "^4.2.2", "@tailwindcss/vite": "^4.2.2", "@tanstack/vue-table": "^8.21.3", "@tanstack/vue-virtual": "^3.13.23", "@tiptap/core": "^3.20.4", "@tiptap/extension-bubble-menu": "^3.20.4", "@tiptap/extension-code": "^3.20.4", "@tiptap/extension-collaboration": "^3.20.4", "@tiptap/extension-drag-handle": "^3.20.4", "@tiptap/extension-drag-handle-vue-3": "^3.20.4", "@tiptap/extension-floating-menu": "^3.20.4", "@tiptap/extension-horizontal-rule": "^3.20.4", "@tiptap/extension-image": "^3.20.4", "@tiptap/extension-mention": "^3.20.4", "@tiptap/extension-node-range": "^3.20.4", "@tiptap/extension-placeholder": "^3.20.4", "@tiptap/markdown": "^3.20.4", "@tiptap/pm": "^3.20.4", "@tiptap/starter-kit": "^3.20.4", "@tiptap/suggestion": "^3.20.4", "@tiptap/vue-3": "^3.20.4", "@unhead/vue": "^2.1.12", "@vueuse/core": "^14.2.1", "@vueuse/integrations": "^14.2.1", "@vueuse/shared": "^14.2.1", "colortranslator": "^5.0.0", "consola": "^3.4.2", "defu": "^6.1.4", "embla-carousel-auto-height": "^8.6.0", "embla-carousel-auto-scroll": "^8.6.0", "embla-carousel-autoplay": "^8.6.0", "embla-carousel-class-names": "^8.6.0", "embla-carousel-fade": "^8.6.0", "embla-carousel-vue": "^8.6.0", "embla-carousel-wheel-gestures": "^8.1.0", "fuse.js": "^7.1.0", "hookable": "^6.1.0", "knitwork": "^1.3.0", "magic-string": "^0.30.21", "mlly": "^1.8.2", "motion-v": "^2.2.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "reka-ui": "2.9.2", "scule": "^1.3.0", "tailwind-merge": "^3.5.0", "tailwind-variants": "^3.2.2", "tailwindcss": "^4.2.2", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unplugin": "^3.0.0", "unplugin-auto-import": "^21.0.0", "unplugin-vue-components": "^31.1.0", "vaul-vue": "0.4.1", "vue-component-type-helpers": "^3.2.6" }, "peerDependencies": { "@inertiajs/vue3": "^2.0.7", "@nuxt/content": "^3.0.0", "joi": "^18.0.0", "superstruct": "^2.0.0", "typescript": "^5.6.3", "valibot": "^1.0.0", "vue-router": "^4.5.0 || ^5.0.0", "yup": "^1.7.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["@inertiajs/vue3", "@nuxt/content", "joi", "superstruct", "valibot", "vue-router", "yup", "zod"], "bin": { "nuxt-ui": "cli/index.mjs" } }, "sha512-AcoM3bljDvxd4PGfzDbY4zyyy9Zsajfagr3il1iki7G8Ji3SG6Xsgy+weug+Hyp76YM5919oc6ciJmram5bf6A=="],
|
"@nuxt/ui": ["@nuxt/ui@4.5.1", "", { "dependencies": { "@floating-ui/dom": "^1.7.5", "@iconify/vue": "^5.0.0", "@internationalized/date": "^3.11.0", "@internationalized/number": "^3.6.5", "@nuxt/fonts": "^0.14.0", "@nuxt/icon": "^2.2.1", "@nuxt/kit": "^4.3.1", "@nuxt/schema": "^4.3.1", "@nuxtjs/color-mode": "^3.5.2", "@standard-schema/spec": "^1.1.0", "@tailwindcss/postcss": "^4.2.1", "@tailwindcss/vite": "^4.2.1", "@tanstack/vue-table": "^8.21.3", "@tanstack/vue-virtual": "^3.13.19", "@tiptap/core": "^3.20.0", "@tiptap/extension-bubble-menu": "^3.20.0", "@tiptap/extension-code": "^3.20.0", "@tiptap/extension-collaboration": "^3.20.0", "@tiptap/extension-drag-handle": "^3.20.0", "@tiptap/extension-drag-handle-vue-3": "^3.20.0", "@tiptap/extension-floating-menu": "^3.20.0", "@tiptap/extension-horizontal-rule": "^3.20.0", "@tiptap/extension-image": "^3.20.0", "@tiptap/extension-mention": "^3.20.0", "@tiptap/extension-node-range": "^3.20.0", "@tiptap/extension-placeholder": "^3.20.0", "@tiptap/markdown": "^3.20.0", "@tiptap/pm": "^3.20.0", "@tiptap/starter-kit": "^3.20.0", "@tiptap/suggestion": "^3.20.0", "@tiptap/vue-3": "^3.20.0", "@unhead/vue": "^2.1.10", "@vueuse/core": "^14.2.1", "@vueuse/integrations": "^14.2.1", "@vueuse/shared": "^14.2.1", "colortranslator": "^5.0.0", "consola": "^3.4.2", "defu": "^6.1.4", "embla-carousel-auto-height": "^8.6.0", "embla-carousel-auto-scroll": "^8.6.0", "embla-carousel-autoplay": "^8.6.0", "embla-carousel-class-names": "^8.6.0", "embla-carousel-fade": "^8.6.0", "embla-carousel-vue": "^8.6.0", "embla-carousel-wheel-gestures": "^8.1.0", "fuse.js": "^7.1.0", "hookable": "^5.5.3", "knitwork": "^1.3.0", "magic-string": "^0.30.21", "mlly": "^1.8.0", "motion-v": "^1.10.3", "ohash": "^2.0.11", "pathe": "^2.0.3", "reka-ui": "2.8.2", "scule": "^1.3.0", "tailwind-merge": "^3.5.0", "tailwind-variants": "^3.2.2", "tailwindcss": "^4.2.1", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unplugin": "^3.0.0", "unplugin-auto-import": "^21.0.0", "unplugin-vue-components": "^31.0.0", "vaul-vue": "0.4.1", "vue-component-type-helpers": "^3.2.5" }, "peerDependencies": { "@inertiajs/vue3": "^2.0.7", "@nuxt/content": "^3.0.0", "joi": "^18.0.0", "superstruct": "^2.0.0", "typescript": "^5.9.3", "valibot": "^1.0.0", "vue-router": "^4.5.0", "yup": "^1.7.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["@inertiajs/vue3", "@nuxt/content", "joi", "superstruct", "valibot", "vue-router", "yup", "zod"], "bin": { "nuxt-ui": "cli/index.mjs" } }, "sha512-5hWgreVPX6EsNCZNoOd2o7m9fTA3fwUMDw+zeYTSAjhSKtAuvkZrBtmez4MUeTv+LO1gknesgvErdIvlUnElTg=="],
|
||||||
|
|
||||||
"@nuxtjs/color-mode": ["@nuxtjs/color-mode@3.5.2", "", { "dependencies": { "@nuxt/kit": "^3.13.2", "pathe": "^1.1.2", "pkg-types": "^1.2.1", "semver": "^7.6.3" } }, "sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA=="],
|
"@nuxtjs/color-mode": ["@nuxtjs/color-mode@3.5.2", "", { "dependencies": { "@nuxt/kit": "^3.13.2", "pathe": "^1.1.2", "pkg-types": "^1.2.1", "semver": "^7.6.3" } }, "sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA=="],
|
||||||
|
|
||||||
@@ -336,121 +336,121 @@
|
|||||||
|
|
||||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"@swc/helpers": ["@swc/helpers@0.5.20", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-2egEBHUMasdypIzrprsu8g+OEVd7Vp2MM3a2eVlM/cyFYto0nGz5BX5BTgh/ShZZI9ed+ozEq+Ngt+rgmUs8tw=="],
|
"@swc/helpers": ["@swc/helpers@0.5.19", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA=="],
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
|
"@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
|
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
|
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
|
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
|
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
|
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
|
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
|
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
|
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
|
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
|
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
|
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.1", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
|
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
|
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="],
|
||||||
|
|
||||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", "tailwindcss": "4.2.2" } }, "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ=="],
|
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.1", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "postcss": "^8.5.6", "tailwindcss": "4.2.1" } }, "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw=="],
|
||||||
|
|
||||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="],
|
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.1", "", { "dependencies": { "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w=="],
|
||||||
|
|
||||||
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
|
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
|
||||||
|
|
||||||
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.23", "", {}, "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg=="],
|
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.21", "", {}, "sha512-ww+fmLHyCbPSf7JNbWZP3g7wl6SdNo3ah5Aiw+0e9FDErkVHLKprYUrwTm7dF646FtEkN/KkAKPYezxpmvOjxw=="],
|
||||||
|
|
||||||
"@tanstack/vue-table": ["@tanstack/vue-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "vue": ">=3.2" } }, "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw=="],
|
"@tanstack/vue-table": ["@tanstack/vue-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "vue": ">=3.2" } }, "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw=="],
|
||||||
|
|
||||||
"@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.23", "", { "dependencies": { "@tanstack/virtual-core": "3.13.23" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-b5jPluAR6U3eOq6GWAYSpj3ugnAIZgGR0e6aGAgyRse0Yu6MVQQ0ZWm9SArSXWtageogn6bkVD8D//c4IjW3xQ=="],
|
"@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.21", "", { "dependencies": { "@tanstack/virtual-core": "3.13.21" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-zneUNdQTcUhoDl6+ek+/O4S9gSZRAc2q7VLscZ4WZnFfZcHc3M7OyVCfSDC3hGuwFqzfL8Cx5bZF6zbGCYwXmw=="],
|
||||||
|
|
||||||
"@tiptap/core": ["@tiptap/core@3.22.0", "", { "peerDependencies": { "@tiptap/pm": "^3.22.0" } }, "sha512-EA/XFbvvz0yRyccqrgOwB9RQe6+uJ8NszjLKH9+3xPE2/+Sa2imax0IqWl7YOXkWihdQVrlpP+EpQF9APKx3jg=="],
|
"@tiptap/core": ["@tiptap/core@3.20.1", "", { "peerDependencies": { "@tiptap/pm": "^3.20.1" } }, "sha512-SwkPEWIfaDEZjC8SEIi4kZjqIYUbRgLUHUuQezo5GbphUNC8kM1pi3C3EtoOPtxXrEbY6e4pWEzW54Pcrd+rVA=="],
|
||||||
|
|
||||||
"@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-WF7K1jtEhkhCZFOoei3QrUHMsM6i9eqXw1IuL6cAX3+CBpqVg89KbP/cJp05dYKU0SO0LJkn87biKVqcnAcN7A=="],
|
"@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-WzNXk/63PQI2fav4Ta6P0GmYRyu8Gap1pV3VUqaVK829iJ6Zt1T21xayATHEHWMK27VT1GLPJkx9Ycr2jfDyQw=="],
|
||||||
|
|
||||||
"@tiptap/extension-bold": ["@tiptap/extension-bold@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-mPG1FzOy2DVaJHHuX/eQPIuYie0kqG07M04nElBY8QlV0oYB4/kd0Aubz+m9czqHx/F9u/L98kmMFhCh2DWk2w=="],
|
"@tiptap/extension-bold": ["@tiptap/extension-bold@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-fz++Qv6Rk/Hov0IYG/r7TJ1Y4zWkuGONe0UN5g0KY32NIMg3HeOHicbi4xsNWTm9uAOl3eawWDkezEMrleObMw=="],
|
||||||
|
|
||||||
"@tiptap/extension-bubble-menu": ["@tiptap/extension-bubble-menu@3.22.0", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-792CUdP0roO17jQJ+fflSJEWfw2cAric61nV2291a2iL7L/6mNJGP4QFim1FqZzfx3/FwHSXEI3NYT6wdUlh8w=="],
|
"@tiptap/extension-bubble-menu": ["@tiptap/extension-bubble-menu@3.20.1", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-XaPvO6aCoWdFnCBus0s88lnj17NR/OopV79i8Qhgz3WMR0vrsL5zsd45l0lZuu9pSvm5VW47SoxakkJiZC1suw=="],
|
||||||
|
|
||||||
"@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.22.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.22.0" } }, "sha512-GUBYUXlNMxfJVpbDQnwNU54rqIBHEIQ7KyPptghSzvmnwMlr6n10OmYSGSVvNpOAIYkHkuhzkwgUDeU+YzWbjg=="],
|
"@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.20.1", "", { "peerDependencies": { "@tiptap/extension-list": "^3.20.1" } }, "sha512-mbrlvOZo5OF3vLhp+3fk9KuL/6J/wsN0QxF6ZFRAHzQ9NkJdtdfARcBeBnkWXGN8inB6YxbTGY1/E4lmBkOpOw=="],
|
||||||
|
|
||||||
"@tiptap/extension-code": ["@tiptap/extension-code@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-JGxByyyUdR0yRt1mOxnA2dp6PmI9pr6C846UkZtuOCwhBOBLkoBGkZqW4FytLPOfWGVJpm7w5tx7h1n5uNwfag=="],
|
"@tiptap/extension-code": ["@tiptap/extension-code@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-509DHINIA/Gg+eTG7TEkfsS8RUiPLH5xZNyLRT0A1oaoaJmECKfrV6aAm05IdfTyqDqz6LW5pbnX6DdUC4keug=="],
|
||||||
|
|
||||||
"@tiptap/extension-code-block": ["@tiptap/extension-code-block@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-HtnYHj6yHVy2dKs02j5dyEehWQMGOGRMiZBkefY3TwSSNzGESVcFfDV+Xr87j7zDGYvY16vOtVmyRTOTqPn49A=="],
|
"@tiptap/extension-code-block": ["@tiptap/extension-code-block@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-vKejwBq+Nlj4Ybd3qOyDxIQKzYymdNH+8eXkKwGShk2nfLJIxq69DCyGvmuHgipIO1qcYPJ149UNpGN+YGcdmA=="],
|
||||||
|
|
||||||
"@tiptap/extension-collaboration": ["@tiptap/extension-collaboration@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0", "@tiptap/y-tiptap": "^3.0.2", "yjs": "^13" } }, "sha512-S/nPIth4/Dr5wmxROk4ELWRYhBXxWt7O6cIi8Fca1rYDBdXofjgt4VDO2iCjnOF0LkIekjQudSOFLWadbM2Z+w=="],
|
"@tiptap/extension-collaboration": ["@tiptap/extension-collaboration@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1", "@tiptap/y-tiptap": "^3.0.2", "yjs": "^13" } }, "sha512-JnwLvyzrutBffHp6YPnf0XyTnhAgqZ9D8JSUKFp0edvai+dxsb+UMlawesBrgAuoQXw3B8YZUo2VFDVdKas1xQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-document": ["@tiptap/extension-document@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-ZdtuBt2KnxIYBtp/VrKiWQ5cLPw1qDKb+sieipBaDWuvhgDNi1kfr//ByEP8xPhcjJfH/C3PCdYYVwIUJwzdqQ=="],
|
"@tiptap/extension-document": ["@tiptap/extension-document@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-9vrqdGmRV7bQCSY3NLgu7UhIwgOCDp4sKqMNsoNRX0aZ021QQMTvBQDPkiRkCf7MNsnWrNNnr52PVnULEn3vFQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-drag-handle": ["@tiptap/extension-drag-handle@3.22.0", "", { "dependencies": { "@floating-ui/dom": "^1.6.13" }, "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/extension-collaboration": "^3.22.0", "@tiptap/extension-node-range": "^3.22.0", "@tiptap/pm": "^3.22.0", "@tiptap/y-tiptap": "^3.0.2" } }, "sha512-OdOCqdgprFrvVhbLCCGX1D0hXo/2SL8o1o50Gy0l1lgiH9vqi/B2LjUJZwYYneQa1K0ME1J4dL4Vp89V4jej0A=="],
|
"@tiptap/extension-drag-handle": ["@tiptap/extension-drag-handle@3.20.1", "", { "dependencies": { "@floating-ui/dom": "^1.6.13" }, "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/extension-collaboration": "^3.20.1", "@tiptap/extension-node-range": "^3.20.1", "@tiptap/pm": "^3.20.1", "@tiptap/y-tiptap": "^3.0.2" } }, "sha512-sIfu5Gt1aZVAagvzr+3Qx+8GE294eU3G6/pYjqNHvf71sDCpdN2gFpeI7WF05foVsCGsH6ieu8x/kTXf5GbNZA=="],
|
||||||
|
|
||||||
"@tiptap/extension-drag-handle-vue-3": ["@tiptap/extension-drag-handle-vue-3@3.22.0", "", { "peerDependencies": { "@tiptap/extension-drag-handle": "^3.22.0", "@tiptap/pm": "^3.22.0", "@tiptap/vue-3": "^3.22.0", "vue": "^3.0.0" } }, "sha512-QuA3Fxe/jxJAW1LzUazN5FEA0i8UyUQCRQ46IzpA878RI2yzeaOfxTiY26s0oA7uUaoAyPg/pLgVjIdnc1epfA=="],
|
"@tiptap/extension-drag-handle-vue-3": ["@tiptap/extension-drag-handle-vue-3@3.20.1", "", { "peerDependencies": { "@tiptap/extension-drag-handle": "^3.20.1", "@tiptap/pm": "^3.20.1", "@tiptap/vue-3": "^3.20.1", "vue": "^3.0.0" } }, "sha512-FJonOxcj/I7uBpJBSqfEfU+Fqem5aanEj+EsqG5ZQnxhp7cxbUYZkGH0tBBR7jeIIkt2Jk+1LoCAlYzCWbDLcQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.22.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.22.0" } }, "sha512-yI0aMD4szbNdy/dlglPbZ9Ddc2UucRatJSifmtenCLg7YWyIIYton0T6Uym+FXAEUZ6KsaoNqEKiUbK5cRzZMQ=="],
|
"@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.20.1", "", { "peerDependencies": { "@tiptap/extensions": "^3.20.1" } }, "sha512-K18L9FX4znn+ViPSIbTLOGcIaXMx/gLNwAPE8wPLwswbHhQqdiY1zzdBw6drgOc1Hicvebo2dIoUlSXOZsOEcw=="],
|
||||||
|
|
||||||
"@tiptap/extension-floating-menu": ["@tiptap/extension-floating-menu@3.22.0", "", { "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-6Gg3I6n+YaCJyvpcKheWiOtU9Oy0M3lbwUGdLK7jTxgAG2YOJxEcx2CzDv3PtNcoyAVUVk9Eio21awVMOECLAQ=="],
|
"@tiptap/extension-floating-menu": ["@tiptap/extension-floating-menu@3.20.1", "", { "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-BeDC6nfOesIMn5pFuUnkEjOxGv80sOJ8uk1mdt9/3Fkvra8cB9NIYYCVtd6PU8oQFmJ8vFqPrRkUWrG5tbqnOg=="],
|
||||||
|
|
||||||
"@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.22.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.22.0" } }, "sha512-VsnaTU88PlA/eG9DtUvuB90z5gVZIaH6T/JVTxGasxR4CFsv0L4Zq5awwr0+SsYH9dKepRMgbanVU03c6k1SuA=="],
|
"@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.20.1", "", { "peerDependencies": { "@tiptap/extensions": "^3.20.1" } }, "sha512-kZOtttV6Ai8VUAgEng3h4WKFbtdSNJ6ps7r0cRPY+FctWhVmgNb/JJwwyC+vSilR7nRENAhrA/Cv/RxVlvLw+g=="],
|
||||||
|
|
||||||
"@tiptap/extension-hard-break": ["@tiptap/extension-hard-break@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-F51pt3fgjbtWrY0Uud+5HoJW4f7w/aBZvmoCk19nrEY955vvuQQ2PD/DZtecl4A8fF50PpRjgilrYnnh99l0ew=="],
|
"@tiptap/extension-hard-break": ["@tiptap/extension-hard-break@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-9sKpmg/IIdlLXimYWUZ3PplIRcehv4Oc7V1miTqlnAthMzjMqigDkjjgte4JZV67RdnDJTQkRw8TklCAU28Emg=="],
|
||||||
|
|
||||||
"@tiptap/extension-heading": ["@tiptap/extension-heading@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-SnOUBXzh9Dft7HY0rqaSL/kZKg4W9wlHfpnFPW8aIuewXvFDLKa6PisqxPpHsXSbG21kfs5E0MLdwdXtNP89XA=="],
|
"@tiptap/extension-heading": ["@tiptap/extension-heading@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-unudyfQP6FxnyWinxvPqe/51DG91J6AaJm666RnAubgYMCgym+33kBftx4j4A6qf+ddWYbD00thMNKOnVLjAEQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-9v08PcmJOumVmgGgcuFPZpAk+tf+m7+vaCNsNyf8Ce1i0m3GPSle1ZmxzjDU2FlpaCFrcgoUKlEjKYaFYFCJIg=="],
|
"@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-rjFKFXNntdl0jay8oIGFvvykHlpyQTLmrH3Ag2fj3i8yh6MVvqhtaDomYQbw5sxECd5hBkL+T4n2d2DRuVw/QQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-image": ["@tiptap/extension-image@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-b3n86lWF3thywUrWmZv27EzTGhCBTjifXY/NzPfri3D22cKkxardrJi2WZIc0J65je91oTzqY7SkdQnyIrZksw=="],
|
"@tiptap/extension-image": ["@tiptap/extension-image@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-/GPFSLNdYSZQ0E6VBXSAk0UFtDdn98HT1Aa2tO+STELqc5jAdFB42dfFnTC6KQzTvcUOUYkE2S1Q22YC5H2XNQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-italic": ["@tiptap/extension-italic@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-+qq9QZF44O1MRqk6w1AMDZ8oDBs5AtdDdNEcdXpzVU54cJAtWyEPEfXtD0B68hOUp/RdZjMdL27fp+4Id7C1YA=="],
|
"@tiptap/extension-italic": ["@tiptap/extension-italic@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-ZYRX13Kt8tR8JOzSXirH3pRpi8x30o7LHxZY58uXBdUvr3tFzOkh03qbN523+diidSVeHP/aMd/+IrplHRkQug=="],
|
||||||
|
|
||||||
"@tiptap/extension-link": ["@tiptap/extension-link@3.22.0", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-tGMBUAmni532G6R5gnaRvTb6c7+ST1qCHBV0p5kGGzdHaQTDd1R7S8fnuA3M7+6Sruc82iIY+Ur+6Tusvo/vLA=="],
|
"@tiptap/extension-link": ["@tiptap/extension-link@3.20.1", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-oYTTIgsQMqpkSnJAuAc+UtIKMuI4lv9e1y4LfI1iYm6NkEUHhONppU59smhxHLzb3Ww7YpDffbp5IgDTAiJztA=="],
|
||||||
|
|
||||||
"@tiptap/extension-list": ["@tiptap/extension-list@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-NfSCAgX44NVLib6aN4HmsP1wi6fFfK3dt6TBb9EgcR82nzq6n7dq7VEBw9V1aKqeXQEtNpqMnQFd0SDayweyfQ=="],
|
"@tiptap/extension-list": ["@tiptap/extension-list@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-euBRAn0mkV7R2VEE+AuOt3R0j9RHEMFXamPFmtvTo8IInxDClusrm6mJoDjS8gCGAXsQCRiAe1SCQBPgGbOOwg=="],
|
||||||
|
|
||||||
"@tiptap/extension-list-item": ["@tiptap/extension-list-item@3.22.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.22.0" } }, "sha512-9cFvFLEtf0bnOc/LaGeX2D+c9wOxeqhzgabUl2Ztz8Xzoby4JtamXnrIpb7DG7hZMf3luJMF8bz0HSvAnNiISQ=="],
|
"@tiptap/extension-list-item": ["@tiptap/extension-list-item@3.20.1", "", { "peerDependencies": { "@tiptap/extension-list": "^3.20.1" } }, "sha512-tzgnyTW82lYJkUnadYbatwkI9dLz/OWRSWuFpQPRje/ItmFMWuQ9c9NDD8qLbXPdEYnvrgSAA+ipCD/1G0qA0Q=="],
|
||||||
|
|
||||||
"@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.22.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.22.0" } }, "sha512-uCtr5/g+Cwkmsb/VLctgo4VjKm0jv52moAmDyr/TLRjW94gnSLhwXFKzyd7BNIXBQHDyS44UEIJFD3ul4dUKdw=="],
|
"@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.20.1", "", { "peerDependencies": { "@tiptap/extension-list": "^3.20.1" } }, "sha512-Dr0xsQKx0XPOgDg7xqoWwfv7FFwZ3WeF3eOjqh3rDXlNHMj1v+UW5cj1HLphrsAZHTrVTn2C+VWPJkMZrSbpvQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-mention": ["@tiptap/extension-mention@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0", "@tiptap/suggestion": "^3.22.0" } }, "sha512-Ndjn8YTcZMyyEV5cm+3TupRsQ4b8+4gRrZXfsqbEbXN/IGXgl+ObBx+bxBU+aAe2ZRgqEeuVIBIVWBzYaxLxrg=="],
|
"@tiptap/extension-mention": ["@tiptap/extension-mention@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1", "@tiptap/suggestion": "^3.20.1" } }, "sha512-KOGokj7oH1QpcM8P02V+o6wHsVE0g7XEtdIy2vtq2vlFE3npNNNFkMa8F8VWX6qyC+VeVrNU6SIzS5MFY2TORA=="],
|
||||||
|
|
||||||
"@tiptap/extension-node-range": ["@tiptap/extension-node-range@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-tize9T79ABbOmkw3+7k8cFjSU/JMpzv1JyvFXuGD7lmiHSrFH9Ll8uOGJC7qhCGJWMsjVvrKmBHSOGbzfJI5hQ=="],
|
"@tiptap/extension-node-range": ["@tiptap/extension-node-range@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-+W/mQJxlkXMcwldWUqwdoR0eniJ1S9cVJoAy2Lkis0NhILZDWVNGKl9J4WFoCOXn8Myr17IllIxRYvAXJJ4FHQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.22.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.22.0" } }, "sha512-B5JSJ2Xe2KPIYYG7jpZHVeAku/VJB+CCgPYl+qIHjZ4JGTnW23qkIA+6dWk6WljGmhQL1qusxZZn4UAnZtBLeQ=="],
|
"@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.20.1", "", { "peerDependencies": { "@tiptap/extension-list": "^3.20.1" } }, "sha512-Y+3Ad7OwAdagqdYwCnbqf7/to5ypD4NnUNHA0TXRCs7cAHRA8AdgPoIcGFpaaSpV86oosNU3yfeJouYeroffog=="],
|
||||||
|
|
||||||
"@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-fwkPvbGI3xvzWrTJVGZVocgA99Pgqd5kW7iv7MEWlI9uOUa6Ifu31/seHV7j+QDW3y3mADcx+zyhxcMVELtLjA=="],
|
"@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-QFrAtXNyv7JSnomMQc1nx5AnG9mMznfbYJAbdOQYVdbLtAzTfiTuNPNbQrufy5ZGtGaHxDCoaygu2QEfzaKG+Q=="],
|
||||||
|
|
||||||
"@tiptap/extension-placeholder": ["@tiptap/extension-placeholder@3.22.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.22.0" } }, "sha512-1GCc87DIPG3jgLH1Xg3ZW11LEzEWnpinLBIZ8RQqrEH0UwtRUW3QsbvwnKKgbbAEtA91GAdnc1eUQ0dpn+tSeQ=="],
|
"@tiptap/extension-placeholder": ["@tiptap/extension-placeholder@3.20.1", "", { "peerDependencies": { "@tiptap/extensions": "^3.20.1" } }, "sha512-k+jfbCugYGuIFBdojukgEopGazIMOgHrw46FnyN2X/6ICOIjQP2rh2ObslrsUOsJYoEevxCsNF9hZl1HvWX66g=="],
|
||||||
|
|
||||||
"@tiptap/extension-strike": ["@tiptap/extension-strike@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-gCgFr1sIcqrJeV5Gdrh8KVZHA+0B1FpFBuOi6FzMyVfBB2sBBqKnjoInYTkPXXdP49Qu8L8hi4luFQtoj4zGzA=="],
|
"@tiptap/extension-strike": ["@tiptap/extension-strike@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-EYgyma10lpsY+rwbVQL9u+gA7hBlKLSMFH7Zgd37FSxukOjr+HE8iKPQQ+SwbGejyDsPlLT8Z5Jnuxo5Ng90Pg=="],
|
||||||
|
|
||||||
"@tiptap/extension-text": ["@tiptap/extension-text@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-FQ3lBRswZbSEbtxOnDF4T7pdsZRmKh/8q+M29zXaDHGfBc6nuGNPlNKSIy0Iryjhf/YmMVaWDpHvzk56KD7QtA=="],
|
"@tiptap/extension-text": ["@tiptap/extension-text@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-7PlIbYW8UenV6NPOXHmv8IcmPGlGx6HFq66RmkJAOJRPXPkTLAiX0N8rQtzUJ6jDEHqoJpaHFEHJw0xzW1yF+A=="],
|
||||||
|
|
||||||
"@tiptap/extension-underline": ["@tiptap/extension-underline@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-AxQOnXQwYmZNjagkEoCZZqbpJbLVmBcu1ivJ9dE0SAQsr1wRUp7mAg+g1SqhbMAvrXvv7yhhNevSdQKmXsnFyA=="],
|
"@tiptap/extension-underline": ["@tiptap/extension-underline@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-fmHvDKzwCgnZUwRreq8tYkb1YyEwgzZ6QQkAQ0CsCRtvRMqzerr3Duz0Als4i8voZTuGDEL3VR6nAJbLAb/wPg=="],
|
||||||
|
|
||||||
"@tiptap/extensions": ["@tiptap/extensions@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-En8p1FiFBT3V9CduErCyLPFxDRsYLISb2cCtLKTeYVeCRn2vQZK4B8WuOgHI4IBipz3I3XidmDhra4yt8mmi/w=="],
|
"@tiptap/extensions": ["@tiptap/extensions@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-JRc/v+OBH0qLTdvQ7HvHWTxGJH73QOf1MC0R8NhOX2QnAbg2mPFv1h+FjGa2gfLGuCXBdWQomjekWkUKbC4e5A=="],
|
||||||
|
|
||||||
"@tiptap/markdown": ["@tiptap/markdown@3.22.0", "", { "dependencies": { "marked": "^17.0.1" }, "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-lvYqID2ui4+HEgh/zULQJoKfb7ILe6ahB9IeaJ5R48mrVyeudciqoJV6VmNOaucm/UpKExlDC3I+QlA9sLpYPw=="],
|
"@tiptap/markdown": ["@tiptap/markdown@3.20.1", "", { "dependencies": { "marked": "^17.0.1" }, "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-dNrtP7kmabDomgjv9G/6+JSFL6WraPaFbmKh1eHSYKdDGvIwBfJnVPTV2VS3bP1OuYJEDJN/2ydtiCHyOTrQsQ=="],
|
||||||
|
|
||||||
"@tiptap/pm": ["@tiptap/pm@3.22.0", "", { "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.24.1", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" } }, "sha512-O9kpzNnFX5837kFevwAM8yr7ImLHu8noIwIpoci0AwfJjiBMzfZBejhbzxnKEfTpFWnkvZ8rWohlb6CQdJ6Crg=="],
|
"@tiptap/pm": ["@tiptap/pm@3.20.1", "", { "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.24.1", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" } }, "sha512-6kCiGLvpES4AxcEuOhb7HR7/xIeJWMjZlb6J7e8zpiIh5BoQc7NoRdctsnmFEjZvC19bIasccshHQ7H2zchWqw=="],
|
||||||
|
|
||||||
"@tiptap/starter-kit": ["@tiptap/starter-kit@3.22.0", "", { "dependencies": { "@tiptap/core": "^3.22.0", "@tiptap/extension-blockquote": "^3.22.0", "@tiptap/extension-bold": "^3.22.0", "@tiptap/extension-bullet-list": "^3.22.0", "@tiptap/extension-code": "^3.22.0", "@tiptap/extension-code-block": "^3.22.0", "@tiptap/extension-document": "^3.22.0", "@tiptap/extension-dropcursor": "^3.22.0", "@tiptap/extension-gapcursor": "^3.22.0", "@tiptap/extension-hard-break": "^3.22.0", "@tiptap/extension-heading": "^3.22.0", "@tiptap/extension-horizontal-rule": "^3.22.0", "@tiptap/extension-italic": "^3.22.0", "@tiptap/extension-link": "^3.22.0", "@tiptap/extension-list": "^3.22.0", "@tiptap/extension-list-item": "^3.22.0", "@tiptap/extension-list-keymap": "^3.22.0", "@tiptap/extension-ordered-list": "^3.22.0", "@tiptap/extension-paragraph": "^3.22.0", "@tiptap/extension-strike": "^3.22.0", "@tiptap/extension-text": "^3.22.0", "@tiptap/extension-underline": "^3.22.0", "@tiptap/extensions": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-3V0RysviBKbsvzHuupM30ftb/WLogSgINtIbmGQHZK4Ta1YzwzW63nAWPGKOoFw8r1HRFFO6LjtqrT6iJsPnzQ=="],
|
"@tiptap/starter-kit": ["@tiptap/starter-kit@3.20.1", "", { "dependencies": { "@tiptap/core": "^3.20.1", "@tiptap/extension-blockquote": "^3.20.1", "@tiptap/extension-bold": "^3.20.1", "@tiptap/extension-bullet-list": "^3.20.1", "@tiptap/extension-code": "^3.20.1", "@tiptap/extension-code-block": "^3.20.1", "@tiptap/extension-document": "^3.20.1", "@tiptap/extension-dropcursor": "^3.20.1", "@tiptap/extension-gapcursor": "^3.20.1", "@tiptap/extension-hard-break": "^3.20.1", "@tiptap/extension-heading": "^3.20.1", "@tiptap/extension-horizontal-rule": "^3.20.1", "@tiptap/extension-italic": "^3.20.1", "@tiptap/extension-link": "^3.20.1", "@tiptap/extension-list": "^3.20.1", "@tiptap/extension-list-item": "^3.20.1", "@tiptap/extension-list-keymap": "^3.20.1", "@tiptap/extension-ordered-list": "^3.20.1", "@tiptap/extension-paragraph": "^3.20.1", "@tiptap/extension-strike": "^3.20.1", "@tiptap/extension-text": "^3.20.1", "@tiptap/extension-underline": "^3.20.1", "@tiptap/extensions": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-opqWxL/4OTEiqmVC0wsU4o3JhAf6LycJ2G/gRIZVAIFLljI9uHfpPMTFGxZ5w9IVVJaP5PJysfwW/635kKqkrw=="],
|
||||||
|
|
||||||
"@tiptap/suggestion": ["@tiptap/suggestion@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-qdZ4v7sXkM4p2+M7OWCGVoNmHmiaHhiGQN3lUzaNG7mG1gZ2SDirSaeOfL8lHxpnPlGzmE4tUe5eCWMpNKHypA=="],
|
"@tiptap/suggestion": ["@tiptap/suggestion@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-ng7olbzgZhWvPJVJygNQK5153CjquR2eJXpkLq7bRjHlahvt4TH4tGFYvGdYZcXuzbe2g9RoqT7NaPGL9CUq9w=="],
|
||||||
|
|
||||||
"@tiptap/vue-3": ["@tiptap/vue-3@3.22.0", "", { "optionalDependencies": { "@tiptap/extension-bubble-menu": "^3.22.0", "@tiptap/extension-floating-menu": "^3.22.0" }, "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0", "vue": "^3.0.0" } }, "sha512-CVThMRtCAYmz+N5RtnxRAjfPZwavdqi0RqjznF3M0Tc5UVrwfJG5YYtE7uzySmzuxW7VrkjTUC+Zx9YM2hQEXQ=="],
|
"@tiptap/vue-3": ["@tiptap/vue-3@3.20.1", "", { "optionalDependencies": { "@tiptap/extension-bubble-menu": "^3.20.1", "@tiptap/extension-floating-menu": "^3.20.1" }, "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1", "vue": "^3.0.0" } }, "sha512-06IsNzIkAC0HPHNSdXf4AgtExnr02BHBLXmUiNrjjhgHdxXDKIB0Yq3hEWEfbWNPr3lr7jK6EaFu2IKFMhoUtQ=="],
|
||||||
|
|
||||||
"@tiptap/y-tiptap": ["@tiptap/y-tiptap@3.0.2", "", { "dependencies": { "lib0": "^0.2.100" }, "peerDependencies": { "prosemirror-model": "^1.7.1", "prosemirror-state": "^1.2.3", "prosemirror-view": "^1.9.10", "y-protocols": "^1.0.1", "yjs": "^13.5.38" } }, "sha512-flMn/YW6zTbc6cvDaUPh/NfLRTXDIqgpBUkYzM74KA1snqQwhOMjnRcnpu4hDFrTnPO6QGzr99vRyXEA7M44WA=="],
|
"@tiptap/y-tiptap": ["@tiptap/y-tiptap@3.0.2", "", { "dependencies": { "lib0": "^0.2.100" }, "peerDependencies": { "prosemirror-model": "^1.7.1", "prosemirror-state": "^1.2.3", "prosemirror-view": "^1.9.10", "y-protocols": "^1.0.1", "yjs": "^13.5.38" } }, "sha512-flMn/YW6zTbc6cvDaUPh/NfLRTXDIqgpBUkYzM74KA1snqQwhOMjnRcnpu4hDFrTnPO6QGzr99vRyXEA7M44WA=="],
|
||||||
|
|
||||||
@@ -474,29 +474,29 @@
|
|||||||
|
|
||||||
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/utils": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg=="],
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.57.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/type-utils": "8.57.0", "@typescript-eslint/utils": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.57.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="],
|
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.57.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/types": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g=="],
|
||||||
|
|
||||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="],
|
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.57.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.57.0", "@typescript-eslint/types": "^8.57.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w=="],
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="],
|
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0" } }, "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw=="],
|
||||||
|
|
||||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="],
|
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.57.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg=="],
|
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0", "@typescript-eslint/utils": "8.57.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="],
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.57.0", "", {}, "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="],
|
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.57.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.57.0", "@typescript-eslint/tsconfig-utils": "8.57.0", "@typescript-eslint/types": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q=="],
|
||||||
|
|
||||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA=="],
|
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.57.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/types": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="],
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg=="],
|
||||||
|
|
||||||
"@unhead/vue": ["@unhead/vue@2.1.12", "", { "dependencies": { "hookable": "^6.0.1", "unhead": "2.1.12" }, "peerDependencies": { "vue": ">=3.5.18" } }, "sha512-zEWqg0nZM8acpuTZE40wkeUl8AhIe0tU0OkilVi1D4fmVjACrwoh5HP6aNqJ8kUnKsoy6D+R3Vi/O+fmdNGO7g=="],
|
"@unhead/vue": ["@unhead/vue@2.1.12", "", { "dependencies": { "hookable": "^6.0.1", "unhead": "2.1.12" }, "peerDependencies": { "vue": ">=3.5.18" } }, "sha512-zEWqg0nZM8acpuTZE40wkeUl8AhIe0tU0OkilVi1D4fmVjACrwoh5HP6aNqJ8kUnKsoy6D+R3Vi/O+fmdNGO7g=="],
|
||||||
|
|
||||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.5", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg=="],
|
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="],
|
||||||
|
|
||||||
"@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="],
|
"@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="],
|
||||||
|
|
||||||
@@ -512,39 +512,39 @@
|
|||||||
|
|
||||||
"@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@1.5.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/parser": "^7.28.0", "@vue/compiler-sfc": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w=="],
|
"@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@1.5.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/parser": "^7.28.0", "@vue/compiler-sfc": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w=="],
|
||||||
|
|
||||||
"@vue/compiler-core": ["@vue/compiler-core@3.6.0-beta.9", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/shared": "3.6.0-beta.9", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-/qlfk4cbU//tgdwWeAbl4kBvH5ZocjJ6CMGigzRKk1So/0jl6kRpFxVbc6oNsYYH6Xj28ByA+msAes1FjExejg=="],
|
"@vue/compiler-core": ["@vue/compiler-core@3.6.0-beta.7", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.6.0-beta.7", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-fYOlA9PCFuf5l8AtKJSxAM+K+MLoM8dyN9Xsa/GDE0Uf6wxFe33AEU6xyVqYMPy6uxp4RtgE4KuQ7JdYvE0Jnw=="],
|
||||||
|
|
||||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.6.0-beta.9", "", { "dependencies": { "@vue/compiler-core": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" } }, "sha512-Bh2h+Co2Vl85jhNjR+yBU1oIujqQ3XIhF7FzODBStvp8yn2WvsXmm5QlvnE7q+smJkfglTbfFcaou8fjR0Y4ag=="],
|
"@vue/compiler-dom": ["@vue/compiler-dom@3.6.0-beta.7", "", { "dependencies": { "@vue/compiler-core": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" } }, "sha512-4myAKKPogd6MAA/oKKn1+uDOPGJ6gB+pivaUxaBqfvXJch1QHSpcUgdHCJqDb9O/99EjcwppkTLGe7cRWMSqHQ=="],
|
||||||
|
|
||||||
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.6.0-beta.9", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/compiler-core": "3.6.0-beta.9", "@vue/compiler-dom": "3.6.0-beta.9", "@vue/compiler-ssr": "3.6.0-beta.9", "@vue/compiler-vapor": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.8", "source-map-js": "^1.2.1" } }, "sha512-13MwFqrIN+AFxR5uT88WYaMTxXycpNNI75sIllz5blXbFaBsDfiegQZj10Pj5G3cgH7WFy2Trs1rTlXrmSgG/w=="],
|
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.6.0-beta.7", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.6.0-beta.7", "@vue/compiler-dom": "3.6.0-beta.7", "@vue/compiler-ssr": "3.6.0-beta.7", "@vue/compiler-vapor": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-bf0NkJb0Hxoj1K8cZKYgduEY0FPP28WnoUGqHvYA/a1cjJfrhaypbiS5u6pEFVXug/JZJF9sI+c4JTHkgqRKDw=="],
|
||||||
|
|
||||||
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.6.0-beta.9", "", { "dependencies": { "@vue/compiler-dom": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" } }, "sha512-ufzHVMJoMz7QzMFop03a8KpWAWUh4hiof/tJXvxiDClIFUY7P5OrKk644fV9qlWgTP7V+eQvA7kVSEVRUgYjvA=="],
|
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.6.0-beta.7", "", { "dependencies": { "@vue/compiler-dom": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" } }, "sha512-fwO4qe6iXZ+SZedurh/vLwjBgjAX/wL5aI/eIECv0C79AgTAlJNjD6dwMafukhnZamQgNKYBxXUOkGl8sqn1PA=="],
|
||||||
|
|
||||||
"@vue/compiler-vapor": ["@vue/compiler-vapor@3.6.0-beta.9", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/compiler-dom": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-1WWCcRjkH/scYJ8dsjNp8+NDf4f9SwQgOcd4Hbl5p0srNPflqrQYvAaJh4N2Szxj5FdkXw2t5uiK35TpFM5uKA=="],
|
"@vue/compiler-vapor": ["@vue/compiler-vapor@3.6.0-beta.7", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-dom": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-fc6ZAFv9yRSoQQVsgN5gGDaUWdTPctDb6lVUtUGgnrCB7n+hQIBwuId7HIeic3PK6HPVZMoEbluNU/ge3DIveA=="],
|
||||||
|
|
||||||
"@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="],
|
"@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="],
|
||||||
|
|
||||||
"@vue/devtools-core": ["@vue/devtools-core@8.1.1", "", { "dependencies": { "@vue/devtools-kit": "^8.1.1", "@vue/devtools-shared": "^8.1.1" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-bCCsSABp1/ot4j8xJEycM6Mtt2wbuucfByr6hMgjbYhrtlscOJypZKvy8f1FyWLYrLTchB5Qz216Lm92wfbq0A=="],
|
"@vue/devtools-core": ["@vue/devtools-core@8.0.7", "", { "dependencies": { "@vue/devtools-kit": "^8.0.7", "@vue/devtools-shared": "^8.0.7" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-PmpiPxvg3Of80ODHVvyckxwEW1Z02VIAvARIZS1xegINn3VuNQLm9iHUmKD+o6cLkMNWV8OG8x7zo0kgydZgdg=="],
|
||||||
|
|
||||||
"@vue/devtools-kit": ["@vue/devtools-kit@8.1.1", "", { "dependencies": { "@vue/devtools-shared": "^8.1.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg=="],
|
"@vue/devtools-kit": ["@vue/devtools-kit@8.0.7", "", { "dependencies": { "@vue/devtools-shared": "^8.0.7", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-H6esJGHGl5q0E9iV3m2EoBQHJ+V83WMW83A0/+Fn95eZ2iIvdsq4+UCS6yT/Fdd4cGZSchx/MdWDreM3WqMsDw=="],
|
||||||
|
|
||||||
"@vue/devtools-shared": ["@vue/devtools-shared@8.1.1", "", {}, "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ=="],
|
"@vue/devtools-shared": ["@vue/devtools-shared@8.0.7", "", {}, "sha512-CgAb9oJH5NUmbQRdYDj/1zMiaICYSLtm+B1kxcP72LBrifGAjUmt8bx52dDH1gWRPlQgxGPqpAMKavzVirAEhA=="],
|
||||||
|
|
||||||
"@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.7.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.56.0", "fast-glob": "^3.3.3", "typescript-eslint": "^8.56.0", "vue-eslint-parser": "^10.4.0" }, "peerDependencies": { "eslint": "^9.10.0 || ^10.0.0", "eslint-plugin-vue": "^9.28.0 || ^10.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-iegbMINVc+seZ/QxtzWiOBozctrHiF2WvGedruu2EbLujg9VuU0FQiNcN2z1ycuaoKKpF4m2qzB5HDEMKbxtIg=="],
|
"@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.7.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.56.0", "fast-glob": "^3.3.3", "typescript-eslint": "^8.56.0", "vue-eslint-parser": "^10.4.0" }, "peerDependencies": { "eslint": "^9.10.0 || ^10.0.0", "eslint-plugin-vue": "^9.28.0 || ^10.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-iegbMINVc+seZ/QxtzWiOBozctrHiF2WvGedruu2EbLujg9VuU0FQiNcN2z1ycuaoKKpF4m2qzB5HDEMKbxtIg=="],
|
||||||
|
|
||||||
"@vue/language-core": ["@vue/language-core@3.2.6", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" } }, "sha512-xYYYX3/aVup576tP/23sEUpgiEnujrENaoNRbaozC1/MA9I6EGFQRJb4xrt/MmUCAGlxTKL2RmT8JLTPqagCkg=="],
|
"@vue/language-core": ["@vue/language-core@3.2.5", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" } }, "sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g=="],
|
||||||
|
|
||||||
"@vue/reactivity": ["@vue/reactivity@3.6.0-beta.9", "", { "dependencies": { "@vue/shared": "3.6.0-beta.9" } }, "sha512-Mo0GjZB/q7LUJplPjBdKqAAk4mawkzvnk2nNf9CEetp4RQJP5/e/DdmgjYBsr/+UVX5ShasCalwdyknuuIepdw=="],
|
"@vue/reactivity": ["@vue/reactivity@3.6.0-beta.7", "", { "dependencies": { "@vue/shared": "3.6.0-beta.7" } }, "sha512-d1Z7U3QqfrpnT1/m8glRDBGlzQVj4J3u7LvCxieyxvrH03ZcR9qZ8hMw9x/Q3K/VcT2BSHp8A8662cqigCiCEA=="],
|
||||||
|
|
||||||
"@vue/runtime-core": ["@vue/runtime-core@3.6.0-beta.9", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" } }, "sha512-IPbO/cmw0BSHec17fSE8Mbtv0UFBiiiGhvlUGiJWTa/eOlbiO+mZ9/PRRUQ+v/tkKuwFSzKye7KOKj5aFO1dgQ=="],
|
"@vue/runtime-core": ["@vue/runtime-core@3.6.0-beta.7", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" } }, "sha512-z6wXT1w0lFYS2uHAYuNZRutxNmg5i4S6yKYVf58/DVu+j/L1eq2ntPNVpPJpof71izXbv14ImvvPUCTTqHwAUA=="],
|
||||||
|
|
||||||
"@vue/runtime-dom": ["@vue/runtime-dom@3.6.0-beta.9", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.9", "@vue/runtime-core": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9", "csstype": "^3.2.3" } }, "sha512-+WV0V1s/uf9iFwZt41lOtJHbIqKyYdEO+nmW0UOnQLjG9ELz6rKuZ92fEim6NPcKCsucv2BoB1mK9T82khYqgg=="],
|
"@vue/runtime-dom": ["@vue/runtime-dom@3.6.0-beta.7", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.7", "@vue/runtime-core": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7", "csstype": "^3.2.3" } }, "sha512-purk/iT3R5y2CizeV+YdxHhqPE1yAk3MSptggsCdO0zkShl2NA1/xec1WgVg1OL6IV0xNrknsMxl9Fit4g44hA=="],
|
||||||
|
|
||||||
"@vue/runtime-vapor": ["@vue/runtime-vapor@3.6.0-beta.9", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" }, "peerDependencies": { "@vue/runtime-dom": "3.6.0-beta.9" } }, "sha512-UlAziGF77byUa84RgkIdCT4bleZbB4UvQvmxdGHFfscGylSABBZHAMR8WJIwBsy1MsZnlpRSSeDAYB20RicrhQ=="],
|
"@vue/runtime-vapor": ["@vue/runtime-vapor@3.6.0-beta.7", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" }, "peerDependencies": { "@vue/runtime-dom": "3.6.0-beta.7" } }, "sha512-H+XdbkqtBI+9gsS957Om94qpTx+keklzvO6rllls4TcKlRo/xcXsDnMhWjghX0Irg6iM/MgBBK8bQkfUkF+xSg=="],
|
||||||
|
|
||||||
"@vue/server-renderer": ["@vue/server-renderer@3.6.0-beta.9", "", { "dependencies": { "@vue/compiler-ssr": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" }, "peerDependencies": { "vue": "3.6.0-beta.9" } }, "sha512-qyz37g6pmwHvtXbe7CWKm5+VTmCs4mHrC1uDw4OVZlvwDDZb9pWMzW0kOrzhj1iI6sZ4D9k5fYQIdzBqB7aOqw=="],
|
"@vue/server-renderer": ["@vue/server-renderer@3.6.0-beta.7", "", { "dependencies": { "@vue/compiler-ssr": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" }, "peerDependencies": { "vue": "3.6.0-beta.7" } }, "sha512-VqX6+8+NyCC/nqkOvj/vKHeMtBr3Mxo7rccDLNyuSb6o5Mp+itlxdpaD0LD28SqHEaDjqfIUBTnFsfDJLN/cww=="],
|
||||||
|
|
||||||
"@vue/shared": ["@vue/shared@3.6.0-beta.9", "", {}, "sha512-nY6DXeelJsmveeNk5YZYGd8q0ulWXACL8Qs/qvkPC4HQkyhPgf+Ja00AkG2hYsEI8Uixia2UpQl4CbObzw0hag=="],
|
"@vue/shared": ["@vue/shared@3.6.0-beta.7", "", {}, "sha512-BNC0ohwv5hYybiUvZ/7gBjSzWFRxpZ2MzRQ/lH2zYZds085ylmAqMGEwbGlWFD+/WPRBsXQd7+e1hNGFksNuCg=="],
|
||||||
|
|
||||||
"@vue/tsconfig": ["@vue/tsconfig@0.8.1", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="],
|
"@vue/tsconfig": ["@vue/tsconfig@0.8.1", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="],
|
||||||
|
|
||||||
@@ -580,23 +580,23 @@
|
|||||||
|
|
||||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.13", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
|
||||||
|
|
||||||
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
||||||
|
|
||||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||||
|
|
||||||
"brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
|
"brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
|
||||||
|
|
||||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||||
|
|
||||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
||||||
|
|
||||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||||
|
|
||||||
"c12": ["c12@3.3.3", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="],
|
"c12": ["c12@3.3.3", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="],
|
||||||
|
|
||||||
"caniuse-lite": ["caniuse-lite@1.0.30001784", "", {}, "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw=="],
|
"caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="],
|
||||||
|
|
||||||
"chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="],
|
"chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="],
|
||||||
|
|
||||||
@@ -646,7 +646,7 @@
|
|||||||
|
|
||||||
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
|
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
|
||||||
|
|
||||||
"electron-to-chromium": ["electron-to-chromium@1.5.330", "", {}, "sha512-jFNydB5kFtYUobh4IkWUnXeyDbjf/r9gcUEXe1xcrcUxIGfTdzPXA+ld6zBRbwvgIGVzDll/LTIiDztEtckSnA=="],
|
"electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="],
|
||||||
|
|
||||||
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
|
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
|
||||||
|
|
||||||
@@ -666,7 +666,7 @@
|
|||||||
|
|
||||||
"embla-carousel-wheel-gestures": ["embla-carousel-wheel-gestures@8.1.0", "", { "dependencies": { "wheel-gestures": "^2.2.5" }, "peerDependencies": { "embla-carousel": "^8.0.0 || ~8.0.0-rc03" } }, "sha512-J68jkYrxbWDmXOm2n2YHl+uMEXzkGSKjWmjaEgL9xVvPb3HqVmg6rJSKfI3sqIDVvm7mkeTy87wtG/5263XqHQ=="],
|
"embla-carousel-wheel-gestures": ["embla-carousel-wheel-gestures@8.1.0", "", { "dependencies": { "wheel-gestures": "^2.2.5" }, "peerDependencies": { "embla-carousel": "^8.0.0 || ~8.0.0-rc03" } }, "sha512-J68jkYrxbWDmXOm2n2YHl+uMEXzkGSKjWmjaEgL9xVvPb3HqVmg6rJSKfI3sqIDVvm7mkeTy87wtG/5263XqHQ=="],
|
||||||
|
|
||||||
"enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
|
"enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="],
|
||||||
|
|
||||||
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||||
|
|
||||||
@@ -674,13 +674,13 @@
|
|||||||
|
|
||||||
"errx": ["errx@0.1.0", "", {}, "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q=="],
|
"errx": ["errx@0.1.0", "", {}, "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q=="],
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="],
|
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
|
||||||
|
|
||||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||||
|
|
||||||
"eslint": ["eslint@10.1.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.3", "@eslint/config-helpers": "^0.5.3", "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA=="],
|
"eslint": ["eslint@10.0.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.3", "@eslint/config-helpers": "^0.5.2", "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.1.1", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ=="],
|
||||||
|
|
||||||
"eslint-plugin-vue": ["eslint-plugin-vue@10.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^7.1.0", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "vue-eslint-parser": "^10.0.0" }, "optionalPeers": ["@stylistic/eslint-plugin", "@typescript-eslint/parser"] }, "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA=="],
|
"eslint-plugin-vue": ["eslint-plugin-vue@10.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^7.1.0", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "vue-eslint-parser": "^10.0.0" }, "optionalPeers": ["@stylistic/eslint-plugin", "@typescript-eslint/parser"] }, "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA=="],
|
||||||
|
|
||||||
@@ -724,7 +724,7 @@
|
|||||||
|
|
||||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||||
|
|
||||||
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
|
"flatted": ["flatted@3.4.1", "", {}, "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ=="],
|
||||||
|
|
||||||
"fontaine": ["fontaine@0.8.0", "", { "dependencies": { "@capsizecss/unpack": "^4.0.0", "css-tree": "^3.1.0", "magic-regexp": "^0.10.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "ufo": "^1.6.1", "unplugin": "^2.3.10" } }, "sha512-eek1GbzOdWIj9FyQH/emqW1aEdfC3lYRCHepzwlFCm5T77fBSRSyNRKE6/antF1/B1M+SfJXVRQTY9GAr7lnDg=="],
|
"fontaine": ["fontaine@0.8.0", "", { "dependencies": { "@capsizecss/unpack": "^4.0.0", "css-tree": "^3.1.0", "magic-regexp": "^0.10.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "ufo": "^1.6.1", "unplugin": "^2.3.10" } }, "sha512-eek1GbzOdWIj9FyQH/emqW1aEdfC3lYRCHepzwlFCm5T77fBSRSyNRKE6/antF1/B1M+SfJXVRQTY9GAr7lnDg=="],
|
||||||
|
|
||||||
@@ -732,7 +732,7 @@
|
|||||||
|
|
||||||
"fontless": ["fontless@0.2.1", "", { "dependencies": { "consola": "^3.4.2", "css-tree": "^3.1.0", "defu": "^6.1.4", "esbuild": "^0.27.0", "fontaine": "0.8.0", "jiti": "^2.6.1", "lightningcss": "^1.30.2", "magic-string": "^0.30.21", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1", "unifont": "^0.7.4", "unstorage": "^1.17.1" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-mUWZ8w91/mw2KEcZ6gHNoNNmsAq9Wiw2IypIux5lM03nhXm+WSloXGUNuRETNTLqZexMgpt7Aj/v63qqrsWraQ=="],
|
"fontless": ["fontless@0.2.1", "", { "dependencies": { "consola": "^3.4.2", "css-tree": "^3.1.0", "defu": "^6.1.4", "esbuild": "^0.27.0", "fontaine": "0.8.0", "jiti": "^2.6.1", "lightningcss": "^1.30.2", "magic-string": "^0.30.21", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1", "unifont": "^0.7.4", "unstorage": "^1.17.1" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-mUWZ8w91/mw2KEcZ6gHNoNNmsAq9Wiw2IypIux5lM03nhXm+WSloXGUNuRETNTLqZexMgpt7Aj/v63qqrsWraQ=="],
|
||||||
|
|
||||||
"framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
|
"framer-motion": ["framer-motion@12.35.2", "", { "dependencies": { "motion-dom": "^12.35.2", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-dhfuEMaNo0hc+AEqyHiIfiJRNb9U9UQutE9FoKm5pjf7CMitp9xPEF1iWZihR1q86LBmo6EJ7S8cN8QXEy49AA=="],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
@@ -748,11 +748,11 @@
|
|||||||
|
|
||||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
"h3": ["h3@1.15.10", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-YzJeWSkDZxAhvmp8dexjRK5hxziRO7I9m0N53WhvYL5NiWfkUkzssVzY9jvGu0HBoLFW6+duYmNSn6MaZBCCtg=="],
|
"h3": ["h3@1.15.6", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-oi15ESLW5LRthZ+qPCi5GNasY/gvynSKUQxgiovrY63bPAtG59wtM+LSrlcwvOHAXzGrXVLnI97brbkdPF9WoQ=="],
|
||||||
|
|
||||||
"hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="],
|
"hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="],
|
||||||
|
|
||||||
"hookable": ["hookable@6.1.0", "", {}, "sha512-ZoKZSJgu8voGK2geJS+6YtYjvIzu9AOM/KZXsBxr83uhLL++e9pEv/dlgwgy3dvHg06kTz6JOh1hk3C8Ceiymw=="],
|
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||||
|
|
||||||
"human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
|
"human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
|
||||||
|
|
||||||
@@ -842,7 +842,7 @@
|
|||||||
|
|
||||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
|
"lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
||||||
|
|
||||||
"magic-regexp": ["magic-regexp@0.10.0", "", { "dependencies": { "estree-walker": "^3.0.3", "magic-string": "^0.30.12", "mlly": "^1.7.2", "regexp-tree": "^0.1.27", "type-level-regexp": "~0.1.17", "ufo": "^1.5.4", "unplugin": "^2.0.0" } }, "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg=="],
|
"magic-regexp": ["magic-regexp@0.10.0", "", { "dependencies": { "estree-walker": "^3.0.3", "magic-string": "^0.30.12", "mlly": "^1.7.2", "regexp-tree": "^0.1.27", "type-level-regexp": "~0.1.17", "ufo": "^1.5.4", "unplugin": "^2.0.0" } }, "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg=="],
|
||||||
|
|
||||||
@@ -852,7 +852,7 @@
|
|||||||
|
|
||||||
"markdown-it": ["markdown-it@14.1.1", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA=="],
|
"markdown-it": ["markdown-it@14.1.1", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA=="],
|
||||||
|
|
||||||
"marked": ["marked@17.0.5", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-6hLvc0/JEbRjRgzI6wnT2P1XuM1/RrrDEX0kPt0N7jGm1133g6X7DlxFasUIx+72aKAr904GTxhSLDrd5DIlZg=="],
|
"marked": ["marked@17.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ=="],
|
||||||
|
|
||||||
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
||||||
|
|
||||||
@@ -868,17 +868,17 @@
|
|||||||
|
|
||||||
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
|
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
|
||||||
|
|
||||||
"minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
"minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
||||||
|
|
||||||
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
||||||
|
|
||||||
"mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
|
"mlly": ["mlly@1.8.1", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ=="],
|
||||||
|
|
||||||
"motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="],
|
"motion-dom": ["motion-dom@12.35.2", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-pWXFMTwvGDbx1Fe9YL5HZebv2NhvGBzRtiNUv58aoK7+XrsuaydQ0JGRKK2r+bTKlwgSWwWxHbP5249Qr/BNpg=="],
|
||||||
|
|
||||||
"motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="],
|
"motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="],
|
||||||
|
|
||||||
"motion-v": ["motion-v@2.2.0", "", { "dependencies": { "framer-motion": "^12.38.0", "hey-listen": "^1.0.8", "motion-dom": "^12.38.0", "motion-utils": "^12.36.0" }, "peerDependencies": { "@vueuse/core": ">=10.0.0", "vue": ">=3.0.0" } }, "sha512-8rrUBt9UMwy45MfLMwHer9J7xBY/rL31S+0U3ZUAouqjH3AOVHvS0NGuXwF4mfWfvb22rfZEv59ZsBc2f1epxQ=="],
|
"motion-v": ["motion-v@1.10.3", "", { "dependencies": { "framer-motion": "^12.25.0", "hey-listen": "^1.0.8", "motion-dom": "^12.23.23" }, "peerDependencies": { "@vueuse/core": ">=10.0.0", "vue": ">=3.0.0" } }, "sha512-9Ewo/wwGv7FO3PqYJpllBF/Efc7tbeM1iinVrM73s0RUQrnXHwMZCaRX98u4lu0PQCrZghPPfCsQ14pWKIEbnQ=="],
|
||||||
|
|
||||||
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||||
|
|
||||||
@@ -942,7 +942,7 @@
|
|||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||||
|
|
||||||
"pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="],
|
"pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="],
|
||||||
|
|
||||||
@@ -990,9 +990,9 @@
|
|||||||
|
|
||||||
"prosemirror-trailing-node": ["prosemirror-trailing-node@3.0.0", "", { "dependencies": { "@remirror/core-constants": "3.0.0", "escape-string-regexp": "^4.0.0" }, "peerDependencies": { "prosemirror-model": "^1.22.1", "prosemirror-state": "^1.4.2", "prosemirror-view": "^1.33.8" } }, "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ=="],
|
"prosemirror-trailing-node": ["prosemirror-trailing-node@3.0.0", "", { "dependencies": { "@remirror/core-constants": "3.0.0", "escape-string-regexp": "^4.0.0" }, "peerDependencies": { "prosemirror-model": "^1.22.1", "prosemirror-state": "^1.4.2", "prosemirror-view": "^1.33.8" } }, "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ=="],
|
||||||
|
|
||||||
"prosemirror-transform": ["prosemirror-transform@1.12.0", "", { "dependencies": { "prosemirror-model": "^1.21.0" } }, "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w=="],
|
"prosemirror-transform": ["prosemirror-transform@1.11.0", "", { "dependencies": { "prosemirror-model": "^1.21.0" } }, "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw=="],
|
||||||
|
|
||||||
"prosemirror-view": ["prosemirror-view@1.41.7", "", { "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } }, "sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w=="],
|
"prosemirror-view": ["prosemirror-view@1.41.6", "", { "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } }, "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg=="],
|
||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
@@ -1012,7 +1012,7 @@
|
|||||||
|
|
||||||
"regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="],
|
"regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="],
|
||||||
|
|
||||||
"reka-ui": ["reka-ui@2.9.3", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.4.0" } }, "sha512-C9lCVxsSC7uYD0Nbgik1+14FNndHNprZmf0zGQt0ZDYIt5KxXV3zD0hEqNcfRUsEEJvVmoRsUkJnASBVBeaaUw=="],
|
"reka-ui": ["reka-ui@2.8.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.2.0" } }, "sha512-8lTKcJhmG+D3UyJxhBnNnW/720sLzm0pbA9AC1MWazmJ5YchJAyTSl+O00xP/kxBmEN0fw5JqWVHguiFmsGjzA=="],
|
||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
@@ -1056,13 +1056,13 @@
|
|||||||
|
|
||||||
"tailwind-variants": ["tailwind-variants@3.2.2", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="],
|
"tailwind-variants": ["tailwind-variants@3.2.2", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="],
|
||||||
|
|
||||||
"tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
|
"tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="],
|
||||||
|
|
||||||
"tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
|
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||||
|
|
||||||
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
||||||
|
|
||||||
"tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
|
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||||
|
|
||||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||||
|
|
||||||
@@ -1070,7 +1070,7 @@
|
|||||||
|
|
||||||
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
||||||
|
|
||||||
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
"ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
|
||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
@@ -1080,7 +1080,7 @@
|
|||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
"typescript-eslint": ["typescript-eslint@8.58.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/parser": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA=="],
|
"typescript-eslint": ["typescript-eslint@8.57.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.57.0", "@typescript-eslint/parser": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0", "@typescript-eslint/utils": "8.57.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA=="],
|
||||||
|
|
||||||
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
|
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
|
||||||
|
|
||||||
@@ -1104,9 +1104,9 @@
|
|||||||
|
|
||||||
"unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="],
|
"unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="],
|
||||||
|
|
||||||
"unplugin-vue-components": ["unplugin-vue-components@31.1.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.2", "obug": "^2.1.1", "picomatch": "^4.0.3", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-9EbV5ark21A4BOBt6RJGJXCVD2I1eoxTZL1TAvNgYTokcrFIiuxpufb8owyWn7n+z2x8daz/ltZq6IRRKL3ydQ=="],
|
"unplugin-vue-components": ["unplugin-vue-components@31.0.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "obug": "^2.1.1", "picomatch": "^4.0.3", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-4ULwfTZTLuWJ7+S9P7TrcStYLsSRkk6vy2jt/WTfgUEUb0nW9//xxmrfhyHUEVpZ2UKRRwfRb8Yy15PDbVZf+Q=="],
|
||||||
|
|
||||||
"unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
"unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
|
||||||
|
|
||||||
"untyped": ["untyped@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "defu": "^6.1.4", "jiti": "^2.4.2", "knitwork": "^1.2.0", "scule": "^1.3.0" }, "bin": { "untyped": "dist/cli.mjs" } }, "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g=="],
|
"untyped": ["untyped@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "defu": "^6.1.4", "jiti": "^2.4.2", "knitwork": "^1.2.0", "scule": "^1.3.0" }, "bin": { "untyped": "dist/cli.mjs" } }, "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g=="],
|
||||||
|
|
||||||
@@ -1126,17 +1126,17 @@
|
|||||||
|
|
||||||
"vite-plugin-inspect": ["vite-plugin-inspect@11.3.3", "", { "dependencies": { "ansis": "^4.1.0", "debug": "^4.4.1", "error-stack-parser-es": "^1.0.5", "ohash": "^2.0.11", "open": "^10.2.0", "perfect-debounce": "^2.0.0", "sirv": "^3.0.1", "unplugin-utils": "^0.3.0", "vite-dev-rpc": "^1.1.0" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0-0" } }, "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA=="],
|
"vite-plugin-inspect": ["vite-plugin-inspect@11.3.3", "", { "dependencies": { "ansis": "^4.1.0", "debug": "^4.4.1", "error-stack-parser-es": "^1.0.5", "ohash": "^2.0.11", "open": "^10.2.0", "perfect-debounce": "^2.0.0", "sirv": "^3.0.1", "unplugin-utils": "^0.3.0", "vite-dev-rpc": "^1.1.0" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0-0" } }, "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA=="],
|
||||||
|
|
||||||
"vite-plugin-vue-devtools": ["vite-plugin-vue-devtools@8.1.1", "", { "dependencies": { "@vue/devtools-core": "^8.1.1", "@vue/devtools-kit": "^8.1.1", "@vue/devtools-shared": "^8.1.1", "sirv": "^3.0.2", "vite-plugin-inspect": "^11.3.3", "vite-plugin-vue-inspector": "^5.3.2" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-9qTpOmZ2vHpvlI9hdVXAQ1Ry4I8GcBArU7aPi0qfIaV7fQIXy0L1nb6X4mFY2Gw0dYshHuLbIl0Ulb572SCjsQ=="],
|
"vite-plugin-vue-devtools": ["vite-plugin-vue-devtools@8.0.7", "", { "dependencies": { "@vue/devtools-core": "^8.0.7", "@vue/devtools-kit": "^8.0.7", "@vue/devtools-shared": "^8.0.7", "sirv": "^3.0.2", "vite-plugin-inspect": "^11.3.3", "vite-plugin-vue-inspector": "^5.3.2" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" } }, "sha512-BWj/ykGpqVAJVdPyHmSTUm44buz3jPv+6jnvuFdQSRH0kAgP1cEIE4doHiFyqHXOmuB5EQVR/nh2g9YRiRNs9g=="],
|
||||||
|
|
||||||
"vite-plugin-vue-inspector": ["vite-plugin-vue-inspector@5.4.0", "", { "dependencies": { "@babel/core": "^7.23.0", "@babel/plugin-proposal-decorators": "^7.23.0", "@babel/plugin-syntax-import-attributes": "^7.22.5", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-transform-typescript": "^7.22.15", "@vue/babel-plugin-jsx": "^1.1.5", "@vue/compiler-dom": "^3.3.4", "kolorist": "^1.8.0", "magic-string": "^0.30.4" }, "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-Iq/024CydcE46FZqWPU4t4lw4uYOdLnFSO1RNxJVt2qY9zxIjmnkBqhHnYaReWM82kmNnaXs7OkfgRrV2GEjyw=="],
|
"vite-plugin-vue-inspector": ["vite-plugin-vue-inspector@5.3.2", "", { "dependencies": { "@babel/core": "^7.23.0", "@babel/plugin-proposal-decorators": "^7.23.0", "@babel/plugin-syntax-import-attributes": "^7.22.5", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-transform-typescript": "^7.22.15", "@vue/babel-plugin-jsx": "^1.1.5", "@vue/compiler-dom": "^3.3.4", "kolorist": "^1.8.0", "magic-string": "^0.30.4" }, "peerDependencies": { "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" } }, "sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q=="],
|
||||||
|
|
||||||
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
||||||
|
|
||||||
"vue": ["vue@3.6.0-beta.9", "", { "dependencies": { "@vue/compiler-dom": "3.6.0-beta.9", "@vue/compiler-sfc": "3.6.0-beta.9", "@vue/runtime-dom": "3.6.0-beta.9", "@vue/runtime-vapor": "3.6.0-beta.9", "@vue/server-renderer": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-K/+HT52OAZvaNDDMvT+Lfk36e/97PsyJyDWXM0FDfLQKGbEDXsd4pSQz+BJViGssLcltQLSTlSqQlZ6fkGo5CA=="],
|
"vue": ["vue@3.6.0-beta.7", "", { "dependencies": { "@vue/compiler-dom": "3.6.0-beta.7", "@vue/compiler-sfc": "3.6.0-beta.7", "@vue/runtime-dom": "3.6.0-beta.7", "@vue/runtime-vapor": "3.6.0-beta.7", "@vue/server-renderer": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-SlNdNG+c37xycRFIJ/KB9CmpZmWwKovbDTNl00ck0+ZSNZSgb+n6DKLuXtVvGZImNPu6jj+zzaM1gqNB5NeC6Q=="],
|
||||||
|
|
||||||
"vue-chartjs": ["vue-chartjs@5.3.3", "", { "peerDependencies": { "chart.js": "^4.1.1", "vue": "^3.0.0-0 || ^2.7.0" } }, "sha512-jqxtL8KZ6YJ5NTv6XzrzLS7osyegOi28UGNZW0h9OkDL7Sh1396ht4Dorh04aKrl2LiSalQ84WtqiG0RIJb0tA=="],
|
"vue-chartjs": ["vue-chartjs@5.3.3", "", { "peerDependencies": { "chart.js": "^4.1.1", "vue": "^3.0.0-0 || ^2.7.0" } }, "sha512-jqxtL8KZ6YJ5NTv6XzrzLS7osyegOi28UGNZW0h9OkDL7Sh1396ht4Dorh04aKrl2LiSalQ84WtqiG0RIJb0tA=="],
|
||||||
|
|
||||||
"vue-component-type-helpers": ["vue-component-type-helpers@3.2.6", "", {}, "sha512-O02tnvIfOQVmnvoWwuSydwRoHjZVt8UEBR+2p4rT35p8GAy5VTlWP8o5qXfJR/GWCN0nVZoYWsVUvx2jwgdBmQ=="],
|
"vue-component-type-helpers": ["vue-component-type-helpers@3.2.5", "", {}, "sha512-tkvNr+bU8+xD/onAThIe7CHFvOJ/BO6XCOrxMzeytJq40nTfpGDJuVjyCM8ccGZKfAbGk2YfuZyDMXM56qheZQ=="],
|
||||||
|
|
||||||
"vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="],
|
"vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="],
|
||||||
|
|
||||||
@@ -1144,9 +1144,9 @@
|
|||||||
|
|
||||||
"vue-i18n": ["vue-i18n@11.3.0", "", { "dependencies": { "@intlify/core-base": "11.3.0", "@intlify/devtools-types": "11.3.0", "@intlify/shared": "11.3.0", "@vue/devtools-api": "^6.5.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA=="],
|
"vue-i18n": ["vue-i18n@11.3.0", "", { "dependencies": { "@intlify/core-base": "11.3.0", "@intlify/devtools-types": "11.3.0", "@intlify/shared": "11.3.0", "@vue/devtools-api": "^6.5.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA=="],
|
||||||
|
|
||||||
"vue-router": ["vue-router@5.0.4", "", { "dependencies": { "@babel/generator": "^7.28.6", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.0.6", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.8.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.17", "pinia": "^3.0.4", "vue": "^3.5.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia"] }, "sha512-lCqDLCI2+fKVRl2OzXuzdSWmxXFLQRxQbmHugnRpTMyYiT+hNaycV0faqG5FBHDXoYrZ6MQcX87BvbY8mQ20Bg=="],
|
"vue-router": ["vue-router@5.0.3", "", { "dependencies": { "@babel/generator": "^7.28.6", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.0.6", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.8.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.17", "pinia": "^3.0.4", "vue": "^3.5.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia"] }, "sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw=="],
|
||||||
|
|
||||||
"vue-tsc": ["vue-tsc@3.2.6", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.2.6" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-gYW/kWI0XrwGzd0PKc7tVB/qpdeAkIZLNZb10/InizkQjHjnT8weZ/vBarZoj4kHKbUTZT/bAVgoOr8x4NsQ/Q=="],
|
"vue-tsc": ["vue-tsc@3.2.5", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.2.5" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA=="],
|
||||||
|
|
||||||
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
|
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
|
||||||
|
|
||||||
@@ -1166,9 +1166,9 @@
|
|||||||
|
|
||||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
"yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="],
|
"yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
|
||||||
|
|
||||||
"yjs": ["yjs@13.6.30", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ=="],
|
"yjs": ["yjs@13.6.29", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-kHqDPdltoXH+X4w1lVmMtddE3Oeqq48nM40FD5ojTd8xYhQpzIDcfE2keMSU5bAgRPJBe225WTUdyUgj1DtbiQ=="],
|
||||||
|
|
||||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
@@ -1184,23 +1184,21 @@
|
|||||||
|
|
||||||
"@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
"@nuxt/schema/std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
|
"@nuxtjs/color-mode/@nuxt/kit": ["@nuxt/kit@3.21.1", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.3.0", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-QORZRjcuTKgo++XP1Pc2c2gqwRydkaExrIRfRI9vFsPA3AzuHVn5Gfmbv1ic8y34e78mr5DMBvJlelUaeOuajg=="],
|
||||||
|
|
||||||
"@nuxt/ui/reka-ui": ["reka-ui@2.9.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.4.0" } }, "sha512-/t4e6y1hcG+uDuRfpg6tbMz3uUEvRzNco6NeYTufoJeUghy5Iosxos5YL/p+ieAsid84sdMX9OrgDqpEuCJhBw=="],
|
|
||||||
|
|
||||||
"@nuxtjs/color-mode/@nuxt/kit": ["@nuxt/kit@3.21.2", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.3.0", "mlly": "^1.8.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-Bd6m6mrDrqpBEbX+g0rc66/ALd1sxlgdx5nfK9MAYO0yKLTOSK7McSYz1KcOYn3LQFCXOWfvXwaqih/b+REI1g=="],
|
|
||||||
|
|
||||||
"@nuxtjs/color-mode/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
"@nuxtjs/color-mode/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
||||||
|
|
||||||
"@nuxtjs/color-mode/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
"@nuxtjs/color-mode/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
|
"@tailwindcss/node/lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
|
|
||||||
@@ -1208,11 +1206,11 @@
|
|||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
|
"@unhead/vue/hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="],
|
||||||
|
|
||||||
"@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@7.7.9", "", { "dependencies": { "@vue/devtools-shared": "^7.7.9", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA=="],
|
"@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@7.7.9", "", { "dependencies": { "@vue/devtools-shared": "^7.7.9", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA=="],
|
||||||
|
|
||||||
"@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
"anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
|
||||||
|
|
||||||
"c12/rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
|
"c12/rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
|
||||||
|
|
||||||
@@ -1228,7 +1226,7 @@
|
|||||||
|
|
||||||
"markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
"markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||||
|
|
||||||
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||||
|
|
||||||
@@ -1244,6 +1242,8 @@
|
|||||||
|
|
||||||
"unctx/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
"unctx/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||||
|
|
||||||
|
"unhead/hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="],
|
||||||
|
|
||||||
"unimport/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
"unimport/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||||
|
|
||||||
"unimport/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
"unimport/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||||
@@ -1256,11 +1256,9 @@
|
|||||||
|
|
||||||
"vaul-vue/@vueuse/core": ["@vueuse/core@10.11.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.11.1", "@vueuse/shared": "10.11.1", "vue-demi": ">=0.14.8" } }, "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="],
|
"vaul-vue/@vueuse/core": ["@vueuse/core@10.11.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.11.1", "@vueuse/shared": "10.11.1", "vue-demi": ">=0.14.8" } }, "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="],
|
||||||
|
|
||||||
"vaul-vue/reka-ui": ["reka-ui@2.9.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.4.0" } }, "sha512-/t4e6y1hcG+uDuRfpg6tbMz3uUEvRzNco6NeYTufoJeUghy5Iosxos5YL/p+ieAsid84sdMX9OrgDqpEuCJhBw=="],
|
|
||||||
|
|
||||||
"vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
"vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
||||||
|
|
||||||
"vue-router/@vue/devtools-api": ["@vue/devtools-api@8.1.1", "", { "dependencies": { "@vue/devtools-kit": "^8.1.1" } }, "sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw=="],
|
"vue-router/@vue/devtools-api": ["@vue/devtools-api@8.0.7", "", { "dependencies": { "@vue/devtools-kit": "^8.0.7" } }, "sha512-tc1TXAxclsn55JblLkFVcIRG7MeSJC4fWsPjfM7qu/IcmPUYnQ5Q8vzWwBpyDY24ZjmZTUCCwjRSNbx58IhlAA=="],
|
||||||
|
|
||||||
"@nuxtjs/color-mode/@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@nuxtjs/color-mode/@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
@@ -1272,9 +1270,29 @@
|
|||||||
|
|
||||||
"@nuxtjs/color-mode/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"@nuxtjs/color-mode/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
"@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="],
|
"@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
|
||||||
|
|
||||||
"@vue/devtools-api/@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
"@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
|
||||||
|
|
||||||
|
"@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="],
|
||||||
|
|
||||||
"@vue/devtools-api/@vue/devtools-kit/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
"@vue/devtools-api/@vue/devtools-kit/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
||||||
|
|
||||||
@@ -1287,7 +1305,5 @@
|
|||||||
"vaul-vue/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="],
|
"vaul-vue/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="],
|
||||||
|
|
||||||
"vaul-vue/@vueuse/core/@vueuse/shared": ["@vueuse/shared@10.11.1", "", { "dependencies": { "vue-demi": ">=0.14.8" } }, "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA=="],
|
"vaul-vue/@vueuse/core/@vueuse/shared": ["@vueuse/shared@10.11.1", "", { "dependencies": { "vue-demi": ">=0.14.8" } }, "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA=="],
|
||||||
|
|
||||||
"vaul-vue/reka-ui/@vueuse/core": ["@vueuse/core@14.2.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.2.1", "@vueuse/shared": "14.2.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ=="],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
bo/components.d.ts
vendored
18
bo/components.d.ts
vendored
@@ -12,15 +12,12 @@ export {}
|
|||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
CartDetails: typeof import('./src/components/customer/CartDetails.vue')['default']
|
CartDetails: typeof import('./src/components/customer/CartDetails.vue')['default']
|
||||||
CategoryMenu: typeof import('./src/components/inner/CategoryMenu.vue')['default']
|
CategoryMenu: typeof import('./src/components/inner/categoryMenu.vue')['default']
|
||||||
CategoryMenuListing: typeof import('./src/components/inner/categoryMenuListing.vue')['default']
|
|
||||||
copy: typeof import('./src/components/admin/ProductDetailView copy.vue')['default']
|
|
||||||
CountryCurrencySwitch: typeof import('./src/components/inner/CountryCurrencySwitch.vue')['default']
|
|
||||||
Cs_PrivacyPolicyView: typeof import('./src/components/terms/cs_PrivacyPolicyView.vue')['default']
|
Cs_PrivacyPolicyView: typeof import('./src/components/terms/cs_PrivacyPolicyView.vue')['default']
|
||||||
Cs_TermsAndConditionsView: typeof import('./src/components/terms/cs_TermsAndConditionsView.vue')['default']
|
Cs_TermsAndConditionsView: typeof import('./src/components/terms/cs_TermsAndConditionsView.vue')['default']
|
||||||
En_PrivacyPolicyView: typeof import('./src/components/terms/en_PrivacyPolicyView.vue')['default']
|
En_PrivacyPolicyView: typeof import('./src/components/terms/en_PrivacyPolicyView.vue')['default']
|
||||||
En_TermsAndConditionsView: typeof import('./src/components/terms/en_TermsAndConditionsView.vue')['default']
|
En_TermsAndConditionsView: typeof import('./src/components/terms/en_TermsAndConditionsView.vue')['default']
|
||||||
LangSwitch: typeof import('./src/components/inner/LangSwitch.vue')['default']
|
LangSwitch: typeof import('./src/components/inner/langSwitch.vue')['default']
|
||||||
PageAddresses: typeof import('./src/components/customer/PageAddresses.vue')['default']
|
PageAddresses: typeof import('./src/components/customer/PageAddresses.vue')['default']
|
||||||
PageCarts: typeof import('./src/components/customer/PageCarts.vue')['default']
|
PageCarts: typeof import('./src/components/customer/PageCarts.vue')['default']
|
||||||
PageOrders: typeof import('./src/components/customer/PageOrders.vue')['default']
|
PageOrders: typeof import('./src/components/customer/PageOrders.vue')['default']
|
||||||
@@ -33,23 +30,17 @@ declare module 'vue' {
|
|||||||
Pl_TermsAndConditionsView: typeof import('./src/components/terms/pl_TermsAndConditionsView.vue')['default']
|
Pl_TermsAndConditionsView: typeof import('./src/components/terms/pl_TermsAndConditionsView.vue')['default']
|
||||||
ProductCustomization: typeof import('./src/components/customer/components/ProductCustomization.vue')['default']
|
ProductCustomization: typeof import('./src/components/customer/components/ProductCustomization.vue')['default']
|
||||||
ProductDetailView: typeof import('./src/components/admin/ProductDetailView.vue')['default']
|
ProductDetailView: typeof import('./src/components/admin/ProductDetailView.vue')['default']
|
||||||
'ProductDetailView copy': typeof import('./src/components/admin/ProductDetailView copy.vue')['default']
|
|
||||||
ProductEditor: typeof import('./src/components/inner/ProductEditor.vue')['default']
|
|
||||||
ProductVariants: typeof import('./src/components/customer/components/ProductVariants.vue')['default']
|
ProductVariants: typeof import('./src/components/customer/components/ProductVariants.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
ThemeSwitch: typeof import('./src/components/inner/ThemeSwitch.vue')['default']
|
ThemeSwitch: typeof import('./src/components/inner/themeSwitch.vue')['default']
|
||||||
TopBar: typeof import('./src/components/TopBar.vue')['default']
|
TopBar: typeof import('./src/components/TopBar.vue')['default']
|
||||||
TopBarLogin: typeof import('./src/components/TopBarLogin.vue')['default']
|
TopBarLogin: typeof import('./src/components/TopBarLogin.vue')['default']
|
||||||
UAlert: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Alert.vue')['default']
|
UAlert: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Alert.vue')['default']
|
||||||
UAvatar: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Avatar.vue')['default']
|
|
||||||
UButton: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Button.vue')['default']
|
UButton: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Button.vue')['default']
|
||||||
UCard: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Card.vue')['default']
|
UCard: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Card.vue')['default']
|
||||||
UCheckbox: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Checkbox.vue')['default']
|
UCheckbox: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Checkbox.vue')['default']
|
||||||
UDrawer: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Drawer.vue')['default']
|
UDrawer: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Drawer.vue')['default']
|
||||||
UDropdownMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/DropdownMenu.vue')['default']
|
|
||||||
UEditor: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Editor.vue')['default']
|
|
||||||
UEditorToolbar: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/EditorToolbar.vue')['default']
|
|
||||||
UForm: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Form.vue')['default']
|
UForm: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Form.vue')['default']
|
||||||
UFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/FormField.vue')['default']
|
UFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/FormField.vue')['default']
|
||||||
UIcon: typeof import('./node_modules/@nuxt/ui/dist/runtime/vue/components/Icon.vue')['default']
|
UIcon: typeof import('./node_modules/@nuxt/ui/dist/runtime/vue/components/Icon.vue')['default']
|
||||||
@@ -60,9 +51,6 @@ declare module 'vue' {
|
|||||||
UPagination: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Pagination.vue')['default']
|
UPagination: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Pagination.vue')['default']
|
||||||
USelect: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Select.vue')['default']
|
USelect: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Select.vue')['default']
|
||||||
USelectMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default']
|
USelectMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default']
|
||||||
USidebar: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Sidebar.vue')['default']
|
|
||||||
UTable: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Table.vue')['default']
|
UTable: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Table.vue')['default']
|
||||||
UTabs: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Tabs.vue')['default']
|
|
||||||
UTextarea: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Textarea.vue')['default']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,22 +13,21 @@
|
|||||||
"format": "prettier --write --experimental-cli src/"
|
"format": "prettier --write --experimental-cli src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "^4.6.0",
|
"@nuxt/ui": "^4.5.0",
|
||||||
"@tailwindcss/vite": "^4.2.2",
|
"@tailwindcss/vite": "^4.2.0",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"reka-ui": "^2.9.3",
|
"tailwindcss": "^4.2.0",
|
||||||
"tailwindcss": "^4.2.2",
|
|
||||||
"vue": "beta",
|
"vue": "beta",
|
||||||
"vue-chartjs": "^5.3.3",
|
"vue-chartjs": "^5.3.3",
|
||||||
"vue-i18n": "^11.3.0",
|
"vue-i18n": "11",
|
||||||
"vue-router": "^5.0.4"
|
"vue-router": "^5.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node24": "^24.0.4",
|
"@tsconfig/node24": "^24.0.4",
|
||||||
"@types/node": "^24.12.0",
|
"@types/node": "^24.10.13",
|
||||||
"@vitejs/plugin-vue": "^6.0.5",
|
"@vitejs/plugin-vue": "^6.0.4",
|
||||||
"@vue/eslint-config-typescript": "^14.7.0",
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
"@vue/tsconfig": "^0.8.1",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
@@ -36,8 +35,8 @@
|
|||||||
"prettier": "3.8.1",
|
"prettier": "3.8.1",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "beta",
|
"vite": "beta",
|
||||||
"vite-plugin-vue-devtools": "^8.1.1",
|
"vite-plugin-vue-devtools": "^8.0.6",
|
||||||
"vue-tsc": "^3.2.6"
|
"vue-tsc": "^3.2.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
|||||||
@@ -1,46 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { TooltipProvider } from 'reka-ui'
|
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Suspense>
|
|
||||||
<TooltipProvider>
|
|
||||||
<RouterView />
|
|
||||||
</TooltipProvider>
|
|
||||||
</Suspense>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- <template>
|
|
||||||
<component :is="layoutComponent">
|
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</component>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
|
|
||||||
import DefaultLayout from '@/layouts/default.vue'
|
|
||||||
import EmptyLayout from '@/layouts/empty.vue'
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const layouts = {
|
|
||||||
default: DefaultLayout,
|
|
||||||
auth: EmptyLayout
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(route.fullPath)
|
|
||||||
console.log(route.name)
|
|
||||||
console.log(route.matched)
|
|
||||||
|
|
||||||
const layoutComponent = computed(() => {
|
|
||||||
console.log(route.meta);
|
|
||||||
|
|
||||||
return layouts[route.meta.layout as keyof typeof layouts] || DefaultLayout
|
|
||||||
})
|
|
||||||
</script> -->
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ export const uiOptions: NuxtUIOptions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
|
slots: {
|
||||||
|
base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
slots: {
|
slots: {
|
||||||
@@ -37,10 +40,9 @@ export const uiOptions: NuxtUIOptions = {
|
|||||||
},
|
},
|
||||||
selectMenu: {
|
selectMenu: {
|
||||||
slots: {
|
slots: {
|
||||||
// base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
||||||
// content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! z-80',
|
content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! z-80 text-(--black)! dark:text-white!',
|
||||||
// content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! z-80 text-(--black)! dark:text-white!',
|
itemLeadingIcon: 'text-(--black)! dark:text-white!'
|
||||||
// itemLeadingIcon: 'text-(--black)! dark:text-white!'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -56,11 +58,6 @@ export const uiOptions: NuxtUIOptions = {
|
|||||||
content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! bg-(--second-light) dark:bg-(--main-dark)',
|
content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! bg-(--second-light) dark:bg-(--main-dark)',
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
|
||||||
textarea: {
|
|
||||||
slots: {
|
|
||||||
base: 'ring-0! outline-0! rounded-md border border-(--border-light)! dark:border-(--border-dark)! text-base!'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none"><path stroke="#5b5b5b" stroke-width="2" d="M3 11c0-3.771 0-5.657 1.172-6.828S7.229 3 11 3h2c3.771 0 5.657 0 6.828 1.172S21 7.229 21 11v2c0 3.771 0 5.657-1.172 6.828S16.771 21 13 21h-2c-3.771 0-5.657 0-6.828-1.172S3 16.771 3 13z"/><path fill="#5b5b5b" fill-rule="evenodd" d="m19 13.585l-.02-.02c-.39-.39-.726-.726-1.026-.979c-.317-.267-.662-.502-1.088-.63a3 3 0 0 0-1.732 0c-.426.129-.77.363-1.088.63c-.3.253-.636.59-1.025.98l-.029.028c-.307.306-.487.486-.628.601l-.02.017l-.012-.023c-.087-.16-.189-.393-.36-.792l-.053-.124l-.022-.052c-.356-.83-.655-1.528-.95-2.054c-.305-.541-.685-1.05-1.277-1.346a3 3 0 0 0-1.597-.307c-.66.055-1.201.386-1.685.775c-.406.327-.86.77-1.388 1.297V13q0 .774.003 1.411l1.114-1.114c.69-.69 1.15-1.147 1.525-1.45c.37-.298.53-.335.6-.34a1 1 0 0 1 .532.102c.061.031.196.124.43.54c.236.42.493 1.016.877 1.912l.053.124l.017.038c.149.348.287.67.425.924c.145.265.355.583.709.802a2 2 0 0 0 1.398.27c.41-.073.723-.29.956-.482c.222-.184.47-.432.738-.7l.03-.03c.425-.425.701-.7.928-.891c.218-.184.32-.228.376-.245a1 1 0 0 1 .578 0c.056.017.158.061.376.245c.227.191.503.466.929.892l1.35 1.35c.046-.718.054-1.61.056-2.773" clip-rule="evenodd"/><circle cx="16.5" cy="7.5" r="1.5" fill="#5b5b5b"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -5,22 +5,19 @@ body {
|
|||||||
font-family: "Inter", sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
|
||||||
margin-left: 20px
|
|
||||||
}
|
|
||||||
|
|
||||||
.inter {
|
.inter {
|
||||||
font-family: "Inter", sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container{
|
||||||
max-width: 2100px;
|
max-width: 2100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--main-light: #FFFEFB;
|
--main-light: #FFFEFB;
|
||||||
--second-light: #F8FAFC;
|
--second-light: #F5F6FA;
|
||||||
--main-dark: #1A1A1A;
|
|
||||||
|
--main-dark: #212121;
|
||||||
--black: #1A1A1A;
|
--black: #1A1A1A;
|
||||||
|
|
||||||
/* gray */
|
/* gray */
|
||||||
@@ -28,15 +25,13 @@ li {
|
|||||||
--gray-dark: #6B7280;
|
--gray-dark: #6B7280;
|
||||||
|
|
||||||
/* borders */
|
/* borders */
|
||||||
--border-light: #E2E8F0;
|
--border-light: #E8E7E0;
|
||||||
--border-dark: #334155;
|
--border-dark: #3F3E3D;
|
||||||
|
|
||||||
/* text */
|
/* text */
|
||||||
--accent-blue-dark: #0EA5E91A;
|
--accent-blue-dark: #3B82F6;
|
||||||
--accent-blue-light: #E0F2FE;
|
--accent-blue-light:#2563EB;
|
||||||
--text-dark: #FFFEFB;
|
--text-dark: #FFFEFB;
|
||||||
--text-sky-light: #0284C7;
|
|
||||||
--text-sky-dark: #38BDF8;
|
|
||||||
|
|
||||||
/* placeholder */
|
/* placeholder */
|
||||||
--placeholder: #8C8C8A;
|
--placeholder: #8C8C8A;
|
||||||
@@ -45,11 +40,11 @@ li {
|
|||||||
--ui-border-accented: var(--border-light);
|
--ui-border-accented: var(--border-light);
|
||||||
--ui-border: var(--border-light);
|
--ui-border: var(--border-light);
|
||||||
--ui-color-neutral-700: var(--black);
|
--ui-color-neutral-700: var(--black);
|
||||||
--ui-primary: var(--accent-blue-light)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--ui-bg: var(--black);
|
--ui-bg: var(--black);
|
||||||
|
--ui-primary: var(--color-gray-500);
|
||||||
--ui-secondary: var(--accent-green-dark);
|
--ui-secondary: var(--accent-green-dark);
|
||||||
--ui-border-accented: var(--border-dark);
|
--ui-border-accented: var(--border-dark);
|
||||||
--ui-text-dimmed: var(--gray-dark);
|
--ui-text-dimmed: var(--gray-dark);
|
||||||
@@ -58,26 +53,21 @@ li {
|
|||||||
--ui-error: var(--dark-red);
|
--ui-error: var(--dark-red);
|
||||||
--border: var(--border-dark);
|
--border: var(--border-dark);
|
||||||
--tw-border-style: var(--border-dark);
|
--tw-border-style: var(--border-dark);
|
||||||
--ui-text-inverted: #fff;
|
|
||||||
--ui-primary: var(--accent-blue-dark)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-form {
|
.label-form {
|
||||||
@apply text-(--gray) dark:text-(--gray-dark) pl-0 md:pl-6 leading-none;
|
@apply text-(--gray) dark:text-(--gray-dark) pl-0 md:pl-6 leading-none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@apply font-medium text-[19px] sm:text-xl md:text-[22px] leading-none text-(--black) dark:text-(--main-light);
|
@apply font-medium text-[19px] sm:text-xl md:text-[22px] leading-none text-(--black) dark:text-(--main-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-title {
|
.column-title {
|
||||||
@apply md:ml-[25px] mb-[25px] sm:mb-[30px];
|
@apply md:ml-[25px] mb-[25px] sm:mb-[30px];
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-title {
|
.form-title {
|
||||||
@apply text-(--accent-green) dark:text-(--accent-green-dark) font-medium;
|
@apply text-(--accent-green) dark:text-(--accent-green-dark) font-medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blue-button {
|
|
||||||
@apply bg-info! text-white!
|
|
||||||
}
|
|
||||||
@@ -1,51 +1,13 @@
|
|||||||
<template>
|
|
||||||
<header
|
|
||||||
class="fixed top-0 left-0 right-0 z-50 bg-white/80 dark:bg-(--black) backdrop-blur-md border-b border-(--border-light) dark:border-(--border-dark)">
|
|
||||||
<div class="px-4">
|
|
||||||
<div class="flex items-center justify-between h-16">
|
|
||||||
<RouterLink :to="{ name: 'home' }" class="flex items-center gap-2">
|
|
||||||
<div class="w-8 h-8 rounded-lg bg-primary text-white flex items-center justify-center">
|
|
||||||
<UIcon name="i-heroicons-clock" class="w-5 h-5" />
|
|
||||||
</div>
|
|
||||||
<span class="font-semibold text-gray-900 dark:text-white">{{ settings.app.name }}</span>
|
|
||||||
</RouterLink>
|
|
||||||
|
|
||||||
<UNavigationMenu :type="'trigger'" :ui="{
|
|
||||||
root: 'justify-center',
|
|
||||||
list: 'gap-4 text-(--black)',
|
|
||||||
linkLabel: 'text-(--black) text-sm!'
|
|
||||||
}" :items="menuItems" class="" />
|
|
||||||
<!-- orientation="vertical" -->
|
|
||||||
<div class="flex items-center gap-12">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<CountryCurrencySwitch />
|
|
||||||
<LangSwitch />
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<ThemeSwitch />
|
|
||||||
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
|
|
||||||
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark) whitespace-nowrap">
|
|
||||||
{{ $t('general.logout') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
import LangSwitch from './inner/LangSwitch.vue'
|
import LangSwitch from './inner/langSwitch.vue'
|
||||||
import ThemeSwitch from './inner/ThemeSwitch.vue'
|
import ThemeSwitch from './inner/themeSwitch.vue'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { currentLang } from '@/router/langs'
|
import { currentLang } from '@/router/langs'
|
||||||
import type { LabelTrans, TopMenuItem } from '@/types'
|
import type { LabelTrans, TopMenuItem } from '@/types'
|
||||||
import type { NavigationMenuItem } from '@nuxt/ui'
|
import type { NavigationMenuItem } from '@nuxt/ui'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import CountryCurrencySwitch from './inner/CountryCurrencySwitch.vue'
|
|
||||||
import { settings } from '@/router/settings'
|
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
let menu = ref()
|
let menu = ref()
|
||||||
@@ -89,3 +51,37 @@ function transformMenu(items: TopMenuItem[], locale: string | undefined): Naviga
|
|||||||
}
|
}
|
||||||
await getTopMenu()
|
await getTopMenu()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header
|
||||||
|
class="fixed top-0 left-0 right-0 z-50 bg-white/80 dark:bg-(--black) backdrop-blur-md border-b border-(--border-light) dark:border-(--border-dark)">
|
||||||
|
<!-- px-4 sm:px-6 lg:px-8 -->
|
||||||
|
<div class="container mx-auto px-4">
|
||||||
|
<div class="flex items-center justify-between h-14">
|
||||||
|
<!-- Logo -->
|
||||||
|
<RouterLink :to="{ name: 'home' }" class="flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 rounded-lg bg-primary text-white flex items-center justify-center">
|
||||||
|
<UIcon name="i-heroicons-clock" class="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<span class="font-semibold text-gray-900 dark:text-white">TimeTracker</span>
|
||||||
|
</RouterLink>
|
||||||
|
|
||||||
|
<UNavigationMenu :type="'trigger'" :ui="{
|
||||||
|
root: 'justify-center',
|
||||||
|
list: 'gap-4'
|
||||||
|
}" :items="menuItems" class="w-full"></UNavigationMenu>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<!-- Language Switcher -->
|
||||||
|
<LangSwitch />
|
||||||
|
<!-- Theme Switcher -->
|
||||||
|
<ThemeSwitch />
|
||||||
|
<!-- Logout Button (only when authenticated) -->
|
||||||
|
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
|
||||||
|
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark)">
|
||||||
|
{{ $t('general.logout') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LangSwitch from './inner/LangSwitch.vue'
|
import LangSwitch from './inner/langSwitch.vue'
|
||||||
import ThemeSwitch from './inner/ThemeSwitch.vue'
|
import ThemeSwitch from './inner/themeSwitch.vue'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<suspense>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="flex flex-col md:flex-row gap-10">
|
<div class="flex gap-10">
|
||||||
<CategoryMenu />
|
<CategoryMenu />
|
||||||
<div class="w-full flex flex-col items-center gap-4">
|
<div class="w-full flex flex-col items-center gap-4">
|
||||||
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" :ui="{
|
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" />
|
||||||
root: 'max-w-100wv overflow-auto!'
|
|
||||||
}" />
|
|
||||||
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
|
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</component>
|
||||||
|
</suspense>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -18,7 +18,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 CategoryMenu from '../inner/CategoryMenu.vue'
|
import CategoryMenu from '../inner/categoryMenu.vue'
|
||||||
import type { Product } from '@/types/product'
|
import type { Product } from '@/types/product'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -153,16 +153,10 @@ async function fetchProductList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToProduct(productId: number, linkRewrite: string) {
|
function goToProduct(productId: number) {
|
||||||
let path = {
|
|
||||||
name: route.name,
|
|
||||||
params: route.params,
|
|
||||||
query: route.query
|
|
||||||
}
|
|
||||||
localStorage.setItem('back_from_product', JSON.stringify(path))
|
|
||||||
router.push({
|
router.push({
|
||||||
name: 'customer-product-details',
|
name: 'customer-product-details',
|
||||||
params: { product_id: productId, link_rewrite: linkRewrite }
|
params: { product_id: productId }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +173,6 @@ const UInput = resolveComponent('UInput')
|
|||||||
const UButton = resolveComponent('UButton')
|
const UButton = resolveComponent('UButton')
|
||||||
const UIcon = resolveComponent('UIcon')
|
const UIcon = resolveComponent('UIcon')
|
||||||
|
|
||||||
import errorImg from '@/assets/error.svg'
|
|
||||||
const columns: TableColumn<Product>[] = [
|
const columns: TableColumn<Product>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: 'product_id',
|
accessorKey: 'product_id',
|
||||||
@@ -216,13 +209,9 @@ const columns: TableColumn<Product>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return h('img', {
|
return h('img', {
|
||||||
src: row.getValue('image_link') as string,
|
src: row.getValue('image_link') as string,
|
||||||
style: 'width:40px;height:40px;object-fit:cover;',
|
style: 'width:40px;height:40px;object-fit:cover;'
|
||||||
onError: (e: Event) => {
|
|
||||||
const target = e.target as HTMLImageElement
|
|
||||||
target.src = errorImg
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'name',
|
accessorKey: 'name',
|
||||||
@@ -281,11 +270,10 @@ const columns: TableColumn<Product>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return h(UButton, {
|
return h(UButton, {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
goToProduct(row.original.product_id, row.original.link_rewrite)
|
goToProduct(row.original.product_id)
|
||||||
},
|
},
|
||||||
class: 'cursor-pointer',
|
color: 'primary',
|
||||||
color: 'info',
|
variant: 'solid'
|
||||||
variant: 'soft'
|
|
||||||
}, () => 'Show product')
|
}, () => 'Show product')
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,526 +0,0 @@
|
|||||||
<template>
|
|
||||||
<component :is="Default || 'div'">
|
|
||||||
<div class="flex items-center gap-2 mb-4">
|
|
||||||
<UIcon name="line-md:arrow-left" class="text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
|
||||||
<p class="cursor-pointer text-(--text-sky-light) dark:text-(--text-sky-dark)" @click="backFromProduct()">
|
|
||||||
Back to products</p>
|
|
||||||
</div>
|
|
||||||
<div class="container mx-auto ">
|
|
||||||
<div
|
|
||||||
class="gap-4 mb-6 bg-slate-50 dark:bg-(--main-dark) border border-(--border-light) dark:border-(--border-dark) p-4 rounded-md">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="flex flex-col items-center gap-3" v-if="!isTranslations">
|
|
||||||
<USelectMenu v-model="toLangId" :items="langs" value-key="id"
|
|
||||||
class="w-48 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm" :searchInput="false">
|
|
||||||
<template #default>
|
|
||||||
<div class="flex flex-col items-start leading-tight">
|
|
||||||
<span class="text-xs text-gray-400">
|
|
||||||
Selected language
|
|
||||||
</span>
|
|
||||||
<span class="font-medium dark:text-white text-black">
|
|
||||||
{{langs.find(l => l.id === toLangId)?.name || 'Select language'}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #item-leading="{ item }">
|
|
||||||
<div class="flex items-center gap-2 cursor-pointer">
|
|
||||||
<span class="text-lg">{{ item.flag }}</span>
|
|
||||||
<span class="font-medium dark:text-white text-black">
|
|
||||||
{{ item.name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="toLangId !== settingStore.shopDefaultLanguage && !isTranslations" class="flex gap-7">
|
|
||||||
<UButton @click="translateToSelectedLanguage" color="info" :loading="translating">
|
|
||||||
Translate from Polish to {{langs.find(l => l.id === toLangId)?.name}}
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
<div v-if="isTranslations" class="flex gap-3 w-full justify-end">
|
|
||||||
<UButton @click="() => {
|
|
||||||
toLangId = settingStore.shopDefaultLanguage
|
|
||||||
isTranslations = false
|
|
||||||
}" color="info" variant="outline">
|
|
||||||
Cancel and back to Polish
|
|
||||||
</UButton>
|
|
||||||
<UButton color="info" @click="productStore.saveProductDescription(productID, toLangId)">
|
|
||||||
Save translations
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- <div class="flex gap-7">
|
|
||||||
<div v-if="isTranslations === false">
|
|
||||||
<UButton @click="() => {
|
|
||||||
fetchForLanguage(toLangId)
|
|
||||||
isShowProductView = true
|
|
||||||
}" class="text-(--accent-blue-light) dark:text-(--accent-blue-dark)"
|
|
||||||
color="neutral" variant="outline">
|
|
||||||
<p class="dark:text-white"> Show product</p>
|
|
||||||
</UButton>
|
|
||||||
<UButton @click="translateToSelectedLanguage" color="primary" :loading="translating"
|
|
||||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) flex items-center! justify-center! px-12">
|
|
||||||
Translate
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex gap-3">
|
|
||||||
<UButton @click="() => {
|
|
||||||
toLangId = settingStore.shopDefaultLanguage
|
|
||||||
isTranslations = false
|
|
||||||
}" color="primary" class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark)">
|
|
||||||
Cancel
|
|
||||||
</UButton>
|
|
||||||
<UButton color="primary" @click="productStore.saveProductDescription(productID, toLangId)"
|
|
||||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark)">
|
|
||||||
Save
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <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">
|
|
||||||
<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" />
|
|
||||||
</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="">
|
|
||||||
<div 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 class="flex flex-col gap-2">
|
|
||||||
<p class="text-[25px] font-bold text-black dark:text-white">
|
|
||||||
{{ productStore.productDescription.name || 'Product Name' }}
|
|
||||||
</p>
|
|
||||||
<p v-html="productStore.productDescription.description_short" class="text-black dark:text-white"></p>
|
|
||||||
|
|
||||||
<div class="space-y-[10px]">
|
|
||||||
<div class="flex items-center gap-1">
|
|
||||||
<UIcon name="lets-icons:done-ring-round-fill"
|
|
||||||
class="text-[20px] light:text-(--accent-green-light) dark:text-(--accent-green-dark)" />
|
|
||||||
<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] light:text-(--accent-green-light) dark:text-(--accent-green-dark)" />
|
|
||||||
<p class="text-[18px] font-bold text-black dark:text-white">
|
|
||||||
{{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<UTabs :items="items" v-model="activeTab" color="info" :content="true" :ui="{
|
|
||||||
list : 'w-auto!'
|
|
||||||
}">
|
|
||||||
<template #description>
|
|
||||||
<div v-if="!isTranslations" class="flex justify-end items-center gap-3 mb-4">
|
|
||||||
<UButton v-if="!isEditing" @click="activeTab === 'usage' ? enableEdit() : 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="isEditing" @click="activeTab === 'usage' ? saveText : 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="isEditing" @click="activeTab === 'usage' ? cancelEdit : cancelDescriptionEdit"
|
|
||||||
color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
|
||||||
Cancel
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
<UEditor v-if="isTranslations" v-slot="{ editor }" v-model="productStore.productDescription.description"
|
|
||||||
content-type="html" :ui="{ base: 'p-8 sm:px-16' }" class="w-full min-h-74" placeholder="Write there ...">
|
|
||||||
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8">
|
|
||||||
<template #link>
|
|
||||||
<EditorLinkPopover :editor="editor" auto-open />
|
|
||||||
</template>
|
|
||||||
</UEditorToolbar>
|
|
||||||
</UEditor>
|
|
||||||
<p v-else v-html="productStore.productDescription.description" class="text-black dark:text-white"></p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #usage>
|
|
||||||
<div class="px-7">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<UEditor v-if="isTranslations" v-slot="{ editor }" v-model="productStore.productDescription.usage"
|
|
||||||
content-type="html" :ui="{ base: 'p-8 sm:px-16' }" class="w-full min-h-74" placeholder="Write there ...">
|
|
||||||
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8">
|
|
||||||
<template #link>
|
|
||||||
<EditorLinkPopover :editor="editor" auto-open />
|
|
||||||
</template>
|
|
||||||
</UEditorToolbar>
|
|
||||||
</UEditor>
|
|
||||||
<p v-else v-html="productStore.productDescription.usage" class="text-black dark:text-white"></p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</UTabs>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</component>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useEditable } from '@/composable/useConteditable';
|
|
||||||
import Default from '@/layouts/default.vue';
|
|
||||||
import { langs } from '@/router/langs';
|
|
||||||
import { useProductStore } from '@/stores/product';
|
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
|
||||||
import type { EditorToolbarItem } from '@nuxt/ui';
|
|
||||||
import { onMounted, ref, watch } from 'vue';
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
const toolbarItems: EditorToolbarItem[][] = [
|
|
||||||
// History controls
|
|
||||||
[{
|
|
||||||
kind: 'undo',
|
|
||||||
icon: 'i-lucide-undo',
|
|
||||||
tooltip: { text: 'Undo' }
|
|
||||||
}, {
|
|
||||||
kind: 'redo',
|
|
||||||
icon: 'i-lucide-redo',
|
|
||||||
tooltip: { text: 'Redo' }
|
|
||||||
}],
|
|
||||||
// Block types
|
|
||||||
[{
|
|
||||||
icon: 'i-lucide-heading',
|
|
||||||
tooltip: { text: 'Headings' },
|
|
||||||
content: {
|
|
||||||
align: 'start'
|
|
||||||
},
|
|
||||||
items: [{
|
|
||||||
kind: 'heading',
|
|
||||||
level: 1,
|
|
||||||
icon: 'i-lucide-heading-1',
|
|
||||||
label: 'Heading 1'
|
|
||||||
}, {
|
|
||||||
kind: 'heading',
|
|
||||||
level: 2,
|
|
||||||
icon: 'i-lucide-heading-2',
|
|
||||||
label: 'Heading 2'
|
|
||||||
}, {
|
|
||||||
kind: 'heading',
|
|
||||||
level: 3,
|
|
||||||
icon: 'i-lucide-heading-3',
|
|
||||||
label: 'Heading 3'
|
|
||||||
}, {
|
|
||||||
kind: 'heading',
|
|
||||||
level: 4,
|
|
||||||
icon: 'i-lucide-heading-4',
|
|
||||||
label: 'Heading 4'
|
|
||||||
}]
|
|
||||||
}, {
|
|
||||||
icon: 'i-lucide-list',
|
|
||||||
tooltip: { text: 'Lists' },
|
|
||||||
content: {
|
|
||||||
align: 'start'
|
|
||||||
},
|
|
||||||
items: [{
|
|
||||||
kind: 'bulletList',
|
|
||||||
icon: 'i-lucide-list',
|
|
||||||
label: 'Bullet List'
|
|
||||||
}, {
|
|
||||||
kind: 'orderedList',
|
|
||||||
icon: 'i-lucide-list-ordered',
|
|
||||||
label: 'Ordered List'
|
|
||||||
}]
|
|
||||||
}, {
|
|
||||||
kind: 'blockquote',
|
|
||||||
icon: 'i-lucide-text-quote',
|
|
||||||
tooltip: { text: 'Blockquote' }
|
|
||||||
}, {
|
|
||||||
kind: 'codeBlock',
|
|
||||||
icon: 'i-lucide-square-code',
|
|
||||||
tooltip: { text: 'Code Block' }
|
|
||||||
}, {
|
|
||||||
kind: 'horizontalRule',
|
|
||||||
icon: 'i-lucide-separator-horizontal',
|
|
||||||
tooltip: { text: 'Horizontal Rule' }
|
|
||||||
}],
|
|
||||||
// Text formatting
|
|
||||||
[{
|
|
||||||
kind: 'mark',
|
|
||||||
mark: 'bold',
|
|
||||||
icon: 'i-lucide-bold',
|
|
||||||
tooltip: { text: 'Bold' }
|
|
||||||
}, {
|
|
||||||
kind: 'mark',
|
|
||||||
mark: 'italic',
|
|
||||||
icon: 'i-lucide-italic',
|
|
||||||
tooltip: { text: 'Italic' }
|
|
||||||
}, {
|
|
||||||
kind: 'mark',
|
|
||||||
mark: 'underline',
|
|
||||||
icon: 'i-lucide-underline',
|
|
||||||
tooltip: { text: 'Underline' }
|
|
||||||
}, {
|
|
||||||
kind: 'mark',
|
|
||||||
mark: 'strike',
|
|
||||||
icon: 'i-lucide-strikethrough',
|
|
||||||
tooltip: { text: 'Strikethrough' }
|
|
||||||
}, {
|
|
||||||
kind: 'mark',
|
|
||||||
mark: 'code',
|
|
||||||
icon: 'i-lucide-code',
|
|
||||||
tooltip: { text: 'Code' }
|
|
||||||
}],
|
|
||||||
// Link
|
|
||||||
[{
|
|
||||||
kind: 'link',
|
|
||||||
icon: 'i-lucide-link',
|
|
||||||
tooltip: { text: 'Link' }
|
|
||||||
}],
|
|
||||||
// Text alignment
|
|
||||||
[{
|
|
||||||
icon: 'i-lucide-align-justify',
|
|
||||||
tooltip: { text: 'Text Align' },
|
|
||||||
content: {
|
|
||||||
align: 'end'
|
|
||||||
},
|
|
||||||
items: [{
|
|
||||||
kind: 'textAlign',
|
|
||||||
align: 'left',
|
|
||||||
icon: 'i-lucide-align-left',
|
|
||||||
label: 'Align Left'
|
|
||||||
}, {
|
|
||||||
kind: 'textAlign',
|
|
||||||
align: 'center',
|
|
||||||
icon: 'i-lucide-align-center',
|
|
||||||
label: 'Align Center'
|
|
||||||
}, {
|
|
||||||
kind: 'textAlign',
|
|
||||||
align: 'right',
|
|
||||||
icon: 'i-lucide-align-right',
|
|
||||||
label: 'Align Right'
|
|
||||||
}, {
|
|
||||||
kind: 'textAlign',
|
|
||||||
align: 'justify',
|
|
||||||
icon: 'i-lucide-align-justify',
|
|
||||||
label: 'Align Justify'
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
function backFromProduct() {
|
|
||||||
let path = localStorage.getItem('back_from_product')
|
|
||||||
|
|
||||||
if (path) {
|
|
||||||
let res = JSON.parse(path)
|
|
||||||
router.push({
|
|
||||||
name: res.name,
|
|
||||||
params: res.params,
|
|
||||||
query: res.query
|
|
||||||
})
|
|
||||||
|
|
||||||
localStorage.removeItem('back_from_product')
|
|
||||||
} else {
|
|
||||||
router.push({
|
|
||||||
name: 'customer-products',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const settingStore = useSettingsStore()
|
|
||||||
const productStore = useProductStore()
|
|
||||||
const isTranslations = ref(false)
|
|
||||||
|
|
||||||
const toLangId = ref<number>(settingStore.shopDefaultLanguage)
|
|
||||||
const productID = ref<number>(0)
|
|
||||||
const translating = ref(false)
|
|
||||||
|
|
||||||
const activeTab = ref('description')
|
|
||||||
const usageRef = ref<HTMLElement | null>(null)
|
|
||||||
const originalUsage = ref('')
|
|
||||||
const isEditing = ref(false)
|
|
||||||
const usageEdit = useEditable(usageRef)
|
|
||||||
const descriptionRef = ref<HTMLElement | null>(null)
|
|
||||||
const descriptionEdit = useEditable(descriptionRef)
|
|
||||||
const originalDescription = ref('')
|
|
||||||
|
|
||||||
const translateToSelectedLanguage = async () => {
|
|
||||||
if (toLangId.value && productID.value) {
|
|
||||||
translating.value = true
|
|
||||||
try {
|
|
||||||
await productStore.translateProductDescription(productID.value, toLangId.value)
|
|
||||||
} finally {
|
|
||||||
translating.value = false
|
|
||||||
enableEdit()
|
|
||||||
enableDescriptionEdit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (productStore.productDescription) {
|
|
||||||
isTranslations.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchForLanguage = async (langId: number | null) => {
|
|
||||||
if (productID.value) {
|
|
||||||
await productStore.getProductDescription(langId, productID.value)
|
|
||||||
removeInlineStylesFromAll(productStore.productDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(toLangId, () => {
|
|
||||||
fetchForLanguage(toLangId.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
const id = route.params.product_id
|
|
||||||
if (id) {
|
|
||||||
productID.value = Number(id)
|
|
||||||
await fetchForLanguage(toLangId.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const items = ref([
|
|
||||||
{ label: 'Description', slot: 'description', name: 'description' },
|
|
||||||
{ label: 'Usage', slot: 'usage', name: 'usage' }
|
|
||||||
])
|
|
||||||
|
|
||||||
// text edit
|
|
||||||
const enableEdit = () => {
|
|
||||||
console.log(usageRef.value, 'usageRef');
|
|
||||||
|
|
||||||
if (usageRef.value) {
|
|
||||||
originalUsage.value = usageRef.value.innerHTML
|
|
||||||
}
|
|
||||||
isEditing.value = true
|
|
||||||
usageEdit.enableEdit()
|
|
||||||
}
|
|
||||||
|
|
||||||
const enableDescriptionEdit = () => {
|
|
||||||
console.log(usageRef.value, 'usageRef');
|
|
||||||
|
|
||||||
if (descriptionRef.value) {
|
|
||||||
originalDescription.value = descriptionRef.value.innerHTML
|
|
||||||
}
|
|
||||||
descriptionEdit.enableEdit()
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveText = () => {
|
|
||||||
if (usageRef.value) {
|
|
||||||
productStore.productDescription.usage = usageRef.value.innerHTML
|
|
||||||
}
|
|
||||||
|
|
||||||
usageEdit.disableEdit()
|
|
||||||
isEditing.value = false
|
|
||||||
|
|
||||||
productStore.saveProductDescription(productID.value, toLangId.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelEdit = () => {
|
|
||||||
if (usageRef.value) {
|
|
||||||
usageRef.value.innerHTML = originalUsage.value
|
|
||||||
}
|
|
||||||
usageEdit.disableEdit()
|
|
||||||
isEditing.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveDescription = async () => {
|
|
||||||
if (descriptionRef.value) {
|
|
||||||
productStore.productDescription.description = descriptionRef.value.innerHTML
|
|
||||||
}
|
|
||||||
|
|
||||||
descriptionEdit.disableEdit()
|
|
||||||
|
|
||||||
await productStore.saveProductDescription(productID.value, toLangId.value)
|
|
||||||
toLangId.value = settingStore.shopDefaultLanguage
|
|
||||||
isTranslations.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelDescriptionEdit = () => {
|
|
||||||
if (descriptionRef.value) {
|
|
||||||
descriptionRef.value.innerHTML = originalDescription.value
|
|
||||||
}
|
|
||||||
descriptionEdit.disableEdit()
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeInlineStylesFromAll(product) {
|
|
||||||
if (!product) return
|
|
||||||
const removeStyles = (html: string) => {
|
|
||||||
if (!html) return ''
|
|
||||||
const div = document.createElement('div')
|
|
||||||
div.innerHTML = html
|
|
||||||
|
|
||||||
div.querySelectorAll('*').forEach(el => {
|
|
||||||
const bg = el.style.background || el.style.backgroundColor
|
|
||||||
if (bg) {
|
|
||||||
el.style.background = ''
|
|
||||||
el.style.backgroundColor = ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
div.querySelectorAll('p').forEach(p => {
|
|
||||||
if (p.querySelector('img')) {
|
|
||||||
p.style.display = 'flex'
|
|
||||||
p.style.gap = '20px'
|
|
||||||
p.style.padding = '30px 0'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
div.querySelectorAll('table').forEach(table => {
|
|
||||||
table.querySelectorAll('tbody').forEach(tbody => {
|
|
||||||
tbody.style.setProperty('background-color', '#F8FAFC', 'important')
|
|
||||||
tbody.querySelectorAll('tr').forEach(tr => {
|
|
||||||
tr.querySelectorAll('td').forEach(td => {
|
|
||||||
td.style.setProperty('padding', '10px', 'important')
|
|
||||||
td.style.setProperty('border-top', '1pt solid black', 'important')
|
|
||||||
td.style.setProperty('border-left', '1pt solid black', 'important')
|
|
||||||
td.style.setProperty('border-bottom', '1pt solid black', 'important')
|
|
||||||
td.style.setProperty('border-right', '1pt solid black', 'important')
|
|
||||||
|
|
||||||
td.querySelectorAll('p').forEach(p => {
|
|
||||||
p.style.setProperty('display', 'flex', 'important')
|
|
||||||
p.style.setProperty('padding', '0px', 'important')
|
|
||||||
p.style.setProperty('align-items', 'start', 'important')
|
|
||||||
p.style.setProperty('color', 'black', 'important')
|
|
||||||
p.style.setProperty('font-size', '14px', 'important')
|
|
||||||
p.querySelectorAll('span').forEach(span => {
|
|
||||||
span.style.setProperty('color', 'black', 'important')
|
|
||||||
span.style.setProperty('font-size', '14px', 'important')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return div.innerHTML
|
|
||||||
}
|
|
||||||
product.description = removeStyles(product.description)
|
|
||||||
product.description_short = removeStyles(product.description_short)
|
|
||||||
product.usage = removeStyles(product.usage)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,62 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="flex items-center gap-2 mb-4">
|
<div class="container my-10 mx-auto ">
|
||||||
<UIcon name="line-md:arrow-left" class="text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
|
||||||
<p class="cursor-pointer text-(--text-sky-light) dark:text-(--text-sky-dark)" @click="backFromProduct()">
|
<div
|
||||||
Back to products</p>
|
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>
|
<div class="flex items-end gap-3">
|
||||||
<div>
|
<USelect v-model="selectedLanguage" :items="availableLangs" variant="outline" class="w-40!"
|
||||||
<div class="gap-4 mb-6 border-b border-(--border-light) dark:border-(--border-dark) py-4">
|
valueKey="iso_code">
|
||||||
<div class="flex flex-wrap gap-4 items-center justify-between">
|
<template #default="{ modelValue }">
|
||||||
<div class="flex flex-col items-center gap-3" v-if="!isTranslations">
|
<div class="flex items-center gap-2">
|
||||||
<USelectMenu v-model="toLangId" :items="langs" value-key="id"
|
<span class="text-md">{{availableLangs.find(x => x.iso_code == modelValue)?.flag}}</span>
|
||||||
class="w-48 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm" :searchInput="false">
|
<span class="font-medium dark:text-white text-black">{{availableLangs.find(x => x.iso_code ==
|
||||||
<template #default>
|
modelValue)?.name}}</span>
|
||||||
<div class="flex flex-col items-start leading-tight">
|
|
||||||
<span class="text-xs text-gray-400">
|
|
||||||
Selected language
|
|
||||||
</span>
|
|
||||||
<span class="font-medium dark:text-white text-black">
|
|
||||||
{{ selectedLangName }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item-leading="{ item }">
|
<template #item-leading="{ item }">
|
||||||
<div class="flex items-center gap-2 cursor-pointer">
|
<div class="flex items-center rounded-md cursor-pointer transition-colors">
|
||||||
<span class="text-lg">{{ item.flag }}</span>
|
<span class="text-md">{{ item.flag }}</span>
|
||||||
<span class="font-medium dark:text-white text-black">
|
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
||||||
{{ item.name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</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>
|
||||||
|
|
||||||
<div v-if="toLangId !== settingStore.shopDefaultLanguage && !isTranslations"
|
<div v-if="translating" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||||
class="flex flex-wrap-reverse justify-between gap-2">
|
<div class="flex flex-col items-center gap-4 p-8 bg-(--main-light) dark:bg-(--main-dark) rounded-lg shadow-xl">
|
||||||
<UButton color="info" variant="outline" v-if="!isEditing" @click="isTranslations = true">
|
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
||||||
<p>Change Text</p>
|
<p class="text-lg font-medium dark:text-white text-black">Translating...</p>
|
||||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-7.5" />
|
|
||||||
</UButton>
|
|
||||||
<UButton @click="translateToSelectedLanguage" color="info" :loading="translating">
|
|
||||||
Translate from Polish to {{langs.find(l => l.id === toLangId)?.name}}
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
<div v-if="isTranslations" class="flex gap-3 w-full justify-end">
|
|
||||||
<UButton @click="() => {
|
|
||||||
handleCancel()
|
|
||||||
isTranslations = false
|
|
||||||
}" color="info" variant="outline">
|
|
||||||
Cancel
|
|
||||||
</UButton>
|
|
||||||
<UButton color="info" @click="async () => {
|
|
||||||
await productStore.saveProductDescription(productID, toLangId)
|
|
||||||
isTranslations = false
|
|
||||||
}">
|
|
||||||
Save
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -66,173 +41,209 @@
|
|||||||
<div v-else-if="productStore.error" class="flex items-center justify-center py-20">
|
<div v-else-if="productStore.error" class="flex items-center justify-center py-20">
|
||||||
<p class="text-red-500">{{ productStore.error }}</p>
|
<p class="text-red-500">{{ productStore.error }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="productStore.productDescription" class="flex items-start gap-30">
|
||||||
<div v-else-if="productStore.productDescription" class="space-y-7.5">
|
<div class="w-80 h-80 bg-(--second-light) dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-6 h-full w-full gap-6">
|
<span class="text-gray-500 dark:text-gray-400">Product Image</span>
|
||||||
<img
|
</div>
|
||||||
class="md:col-span-2 rounded-md bg-white rounded-md border border-(--border-light) dark:border-(--border-dark)"
|
<div class="flex flex-col gap-2">
|
||||||
:src="productStore.productDescription.image_link" :alt="productStore.productDescription.name" @error="(e: Event) => {
|
<p class="text-[25px] font-bold text-black dark:text-white">
|
||||||
const target = e.target as HTMLImageElement
|
{{ productStore.productDescription.name || 'Product Name' }}
|
||||||
target.src = errorImg
|
|
||||||
}">
|
|
||||||
<div class="md:col-span-4 flex flex-col justify-between min-h-full w-full space-y-7.5">
|
|
||||||
<div class="space-y-7.5">
|
|
||||||
<div>
|
|
||||||
<p v-if="!isTranslations" class="text-[25px] font-bold text-black dark:text-white">
|
|
||||||
{{ productStore.productDescription.name || 'Product name not provided' }}
|
|
||||||
</p>
|
</p>
|
||||||
<div class="" v-if="isTranslations">
|
<p v-html="productStore.productDescription.description_short" class="text-black dark:text-white"></p>
|
||||||
<p>Title:</p>
|
<div class="space-y-[10px]">
|
||||||
<UTextarea :rows="1" v-model="productStore.productDescription.name" autoresize :ui="{
|
<div class="flex items-center gap-1">
|
||||||
root: 'w-full bg-white!',
|
<UIcon name="lets-icons:done-ring-round-fill" class="text-[20px] text-green-600" />
|
||||||
base: 'text-2xl!',
|
<p class="text-[16px] font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
||||||
}" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="" v-if="isTranslations">
|
|
||||||
<p>Link rewrite:</p>
|
|
||||||
<UTextarea :rows="1" v-model="productStore.productDescription.link_rewrite" autoresize :ui="{
|
|
||||||
root: 'w-full bg-white!'
|
|
||||||
}" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p v-if="!isTranslations"
|
|
||||||
v-html="productStore.productDescription.description_short || 'No short description available'"
|
|
||||||
class="text-black dark:text-white" />
|
|
||||||
<div class="" v-if="isTranslations">
|
|
||||||
<p>Short description:</p>
|
|
||||||
<ProductEditor v-model="productStore.productDescription.description_short" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-2.5">
|
|
||||||
<div v-if="productStore.productDescription.available_now" class="flex items-center gap-1">
|
|
||||||
<UIcon name="lets-icons:done-ring-round-fill"
|
|
||||||
class="text-[20px] light:text-(--accent-green-light) dark:text-(--accent-green-dark)" />
|
|
||||||
<p>
|
|
||||||
{{ productStore.productDescription.available_now }}
|
{{ productStore.productDescription.available_now }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="productStore.productDescription.delivery_in_stock" class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<UIcon name="marketeq:car-shipping"
|
<UIcon name="marketeq:car-shipping" class="text-[25px] text-green-600" />
|
||||||
class="text-[25px] light:text-(--accent-green-light) dark:text-(--accent-green-dark)" />
|
<p class="text-[18px] font-bold text-black dark:text-white">
|
||||||
<p>
|
{{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}
|
||||||
{{ productStore.productDescription.delivery_in_stock }}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UTabs :items="items" color="info" default-value="0" :ui="{
|
<div v-if="productStore.productDescription" class="mt-16">
|
||||||
list: 'w-auto!',
|
<div class="flex gap-4 my-6">
|
||||||
root: 'items-start!'
|
<UButton @click="activeTab = 'description'"
|
||||||
}">
|
:class="['cursor-pointer', activeTab === 'description' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
||||||
<template #description>
|
variant="outline">
|
||||||
<ProductEditor v-if="isTranslations" v-model="productStore.productDescription.description" />
|
<p class="dark:text-white">Description</p>
|
||||||
<p v-else v-html="productStore.productDescription.description || 'No description available'"
|
</UButton>
|
||||||
class="text-black dark:text-white" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #usage>
|
<UButton @click="activeTab = 'usage'"
|
||||||
<ProductEditor v-if="isTranslations" v-model="productStore.productDescription.usage" />
|
:class="['cursor-pointer', activeTab === 'usage' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
||||||
<p v-else v-html="productStore.productDescription.usage || 'No usage information available'"
|
variant="outline">
|
||||||
class="text-black dark:text-white" />
|
<p class="dark:text-white">Usage</p>
|
||||||
</template>
|
</UButton>
|
||||||
</UTabs>
|
</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 class=""></div>
|
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Default from '@/layouts/default.vue';
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
import { langs } from '@/router/langs';
|
import { useRoute } from 'vue-router'
|
||||||
import { useProductStore } from '@/stores/product';
|
import { useProductStore } from '@/stores/product'
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useEditable } from '@/composable/useConteditable'
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { langs } from '@/router/langs'
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import type { Language } from '@/types'
|
||||||
import ProductEditor from '../inner/ProductEditor.vue';
|
import Default from '@/layouts/default.vue'
|
||||||
import errorImg from '@/assets/error.svg'
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const settingStore = useSettingsStore()
|
const activeTab = ref('description')
|
||||||
const productStore = useProductStore()
|
const productStore = useProductStore()
|
||||||
|
|
||||||
const toLangId = ref<number>(settingStore.shopDefaultLanguage)
|
|
||||||
const productID = ref<number>(0)
|
|
||||||
const translating = ref(false)
|
const translating = ref(false)
|
||||||
const isEditing = ref(false)
|
|
||||||
const isTranslations = ref(false)
|
|
||||||
|
|
||||||
const selectedLangName = computed(() => {
|
const isEditing = ref(false)
|
||||||
return langs.find(l => l.id === toLangId.value)?.name || 'Select language'
|
|
||||||
|
const availableLangs = computed(() => langs)
|
||||||
|
|
||||||
|
const selectedLanguage = ref('en')
|
||||||
|
|
||||||
|
const currentLangId = ref(2)
|
||||||
|
const productID = ref<number>(0)
|
||||||
|
|
||||||
|
// Watch for language changes and refetch product description
|
||||||
|
watch(selectedLanguage, async (newLang: string) => {
|
||||||
|
if (productID.value) {
|
||||||
|
await fetchForLanguage(newLang)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const fetchForLanguage = async (langCode: string) => {
|
||||||
function backFromProduct() {
|
const lang = langs.find((l: Language) => l.iso_code === langCode)
|
||||||
let path = localStorage.getItem('back_from_product')
|
if (lang && productID.value) {
|
||||||
|
await productStore.getProductDescription(lang.id, productID.value)
|
||||||
if (path) {
|
currentLangId.value = lang.id
|
||||||
let res = JSON.parse(path)
|
|
||||||
router.push({
|
|
||||||
name: res.name,
|
|
||||||
params: res.params,
|
|
||||||
query: res.query
|
|
||||||
})
|
|
||||||
|
|
||||||
localStorage.removeItem('back_from_product')
|
|
||||||
} else {
|
|
||||||
router.push({
|
|
||||||
name: 'admin-products',
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const translateToSelectedLanguage = async () => {
|
const translateToSelectedLanguage = async () => {
|
||||||
if (toLangId.value && productID.value) {
|
const targetLang = langs.find((l: Language) => l.iso_code === selectedLanguage.value)
|
||||||
|
if (targetLang && currentLangId.value && productID.value) {
|
||||||
translating.value = true
|
translating.value = true
|
||||||
try {
|
try {
|
||||||
await productStore.translateProductDescription(productID.value, toLangId.value)
|
await productStore.translateProductDescription(productID.value, currentLangId.value, targetLang.id)
|
||||||
|
currentLangId.value = targetLang.id
|
||||||
} finally {
|
} finally {
|
||||||
translating.value = false
|
translating.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (productStore.productDescription) {
|
|
||||||
isTranslations.value = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchForLanguage = async (langId: number | null) => {
|
|
||||||
if (productID.value) {
|
|
||||||
await productStore.getProductDescription(langId, productID.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const handleCancel = async () => {
|
|
||||||
await productStore.getProductDescription(toLangId.value, productID.value)
|
|
||||||
isTranslations.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(toLangId, (lang) => {
|
|
||||||
fetchForLanguage(lang)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const id = route.params.product_id
|
const id = route.params.product_id
|
||||||
if (id) {
|
if (id) {
|
||||||
productID.value = Number(id)
|
productID.value = Number(id)
|
||||||
await fetchForLanguage(toLangId.value)
|
await fetchForLanguage(selectedLanguage.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const items = ref([
|
const descriptionRef = ref<HTMLElement | null>(null)
|
||||||
{ label: 'Description', slot: 'description', name: 'description' },
|
const usageRef = ref<HTMLElement | null>(null)
|
||||||
{ label: 'Usage', slot: 'usage', name: 'usage' }
|
|
||||||
])
|
const descriptionEdit = useEditable(descriptionRef)
|
||||||
|
const usageEdit = useEditable(usageRef)
|
||||||
|
|
||||||
|
const originalDescription = ref('')
|
||||||
|
const originalUsage = ref('')
|
||||||
|
|
||||||
|
const saveDescription = async () => {
|
||||||
|
descriptionEdit.disableEdit()
|
||||||
|
await productStore.saveProductDescription(productID.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelDescriptionEdit = () => {
|
||||||
|
if (descriptionRef.value) {
|
||||||
|
descriptionRef.value.innerHTML = originalDescription.value
|
||||||
|
}
|
||||||
|
descriptionEdit.disableEdit()
|
||||||
|
}
|
||||||
|
|
||||||
|
const enableDescriptionEdit = () => {
|
||||||
|
if (descriptionRef.value) {
|
||||||
|
originalDescription.value = descriptionRef.value.innerHTML
|
||||||
|
}
|
||||||
|
descriptionEdit.enableEdit()
|
||||||
|
}
|
||||||
|
|
||||||
|
const enableEdit = () => {
|
||||||
|
if (usageRef.value) {
|
||||||
|
originalUsage.value = usageRef.value.innerHTML
|
||||||
|
}
|
||||||
|
isEditing.value = true
|
||||||
|
usageEdit.enableEdit()
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveText = () => {
|
||||||
|
usageEdit.disableEdit()
|
||||||
|
isEditing.value = false
|
||||||
|
productStore.saveProductDescription(productID.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelEdit = () => {
|
||||||
|
if (usageRef.value) {
|
||||||
|
usageRef.value.innerHTML = originalUsage.value
|
||||||
|
}
|
||||||
|
usageEdit.disableEdit()
|
||||||
|
isEditing.value = false
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.images {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 70px;
|
||||||
|
margin: 20px 0 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="">
|
<div class="container mx-auto">
|
||||||
<h2
|
<h2
|
||||||
class="font-semibold text-black dark:text-white pb-6 text-2xl">
|
class="font-semibold text-black dark:text-white pb-6 text-2xl">
|
||||||
{{ t('Cart Items') }}
|
{{ t('Cart Items') }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="">
|
<div class="container mx-auto">
|
||||||
<div class="flex flex-col gap-5 mb-6">
|
<div class="flex flex-col gap-5 mb-6">
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Addresses') }}</h1>
|
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Addresses') }}</h1>
|
||||||
<div class="flex md:flex-row flex-col justify-between items-start md:items-center gap-5 md:gap-0">
|
<div class="flex md:flex-row flex-col justify-between items-start md:items-center gap-5 md:gap-0">
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<UInput v-model="searchQuery" type="text" :placeholder="t('Search address')"
|
<UInput v-model="searchQuery" type="text" :placeholder="t('Search address')"
|
||||||
class="bg-white dark:bg-gray-800 text-black dark:text-white absolute" />
|
class="bg-white dark:bg-gray-800 text-black dark:text-white absolute" />
|
||||||
<UIcon name="ic:baseline-search"
|
<UIcon name="ic:baseline-search"
|
||||||
class="text-[20px] text-(--text-sky-light) dark:text-(--text-sky-dark) relative left-40" />
|
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark) relative left-40" />
|
||||||
</div>
|
</div>
|
||||||
<UButton color="primary" @click="openCreateModal"
|
<UButton color="primary" @click="openCreateModal"
|
||||||
class="bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white hover:bg-(--accent-blue-dark) dark:hover:bg-(--accent-blue-light)">
|
class="bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white hover:bg-(--accent-blue-dark) dark:hover:bg-(--accent-blue-light)">
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<UIcon name="material-symbols:delete" class="text-[18px]" />
|
<UIcon name="material-symbols:delete" class="text-[18px]" />
|
||||||
</button>
|
</button>
|
||||||
<UButton size="sm" color="neutral" variant="outline" @click="openEditModal(address)"
|
<UButton size="sm" color="neutral" variant="outline" @click="openEditModal(address)"
|
||||||
class="text-(--text-sky-light) dark:text-(--text-sky-dark) text-[13px]">
|
class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) text-[13px]">
|
||||||
{{ t('edit') }}
|
{{ t('edit') }}
|
||||||
<UIcon name="ic:sharp-edit" class="text-[15px]" />
|
<UIcon name="ic:sharp-edit" class="text-[15px]" />
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="flex flex-col gap-5 md:gap-10">
|
<div class="container mx-auto flex flex-col gap-5 md:gap-10">
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Shopping Cart') }}</h1>
|
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Shopping Cart') }}</h1>
|
||||||
<div class="flex flex-col lg:flex-row gap-5 md:gap-10">
|
<div class="flex flex-col lg:flex-row gap-5 md:gap-10">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
name: 'customer-product', params: {
|
name: 'customer-product', params: {
|
||||||
product_id: '51'
|
product_id: '51'
|
||||||
}
|
}
|
||||||
}" class="inline-block mt-4 text-(--text-sky-light) dark:text-(--text-sky-dark) hover:underline">
|
}" class="inline-block mt-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
|
||||||
{{ t('Continue Shopping') }}
|
{{ t('Continue Shopping') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between mb-6">
|
<div class="flex justify-between mb-6">
|
||||||
<span class="text-black dark:text-white font-semibold text-lg">{{ t('Total') }}</span>
|
<span class="text-black dark:text-white font-semibold text-lg">{{ t('Total') }}</span>
|
||||||
<span class="text-(--text-sky-light) dark:text-(--text-sky-dark) font-bold text-lg">${{
|
<span class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) font-bold text-lg">${{
|
||||||
cartStore.orderTotal.toFixed(2) }}</span>
|
cartStore.orderTotal.toFixed(2) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
||||||
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
||||||
<input type="radio" :value="address.id" v-model="selectedAddress"
|
<input type="radio" :value="address.id" v-model="selectedAddress"
|
||||||
class="mt-1 w-4 h-4 text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
class="mt-1 w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-black dark:text-white font-medium">{{ address.street }}</p>
|
<p class="text-black dark:text-white font-medium">{{ address.street }}</p>
|
||||||
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.zipCode }}, {{ address.city }}</p>
|
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.zipCode }}, {{ address.city }}</p>
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
||||||
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
||||||
<RouterLink :to="{ name: 'addresses' }"
|
<RouterLink :to="{ name: 'addresses' }"
|
||||||
class="inline-block mt-2 text-(--text-sky-light) dark:text-(--text-sky-dark) hover:underline">
|
class="inline-block mt-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
|
||||||
{{ t('Add Address') }}
|
{{ t('Add Address') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,11 +131,11 @@
|
|||||||
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
||||||
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
||||||
<input type="radio" :value="method.id" v-model="selectedDeliveryMethod"
|
<input type="radio" :value="method.id" v-model="selectedDeliveryMethod"
|
||||||
class="w-4 h-4 text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
class="w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-black dark:text-white font-medium">{{ method.name }}</span>
|
<span class="text-black dark:text-white font-medium">{{ method.name }}</span>
|
||||||
<span class="text-(--text-sky-light) dark:text-(--text-sky-dark) font-medium">
|
<span class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) font-medium">
|
||||||
{{ method.price > 0 ? `$${method.price.toFixed(2)}` : t('Free') }}
|
{{ method.price > 0 ? `$${method.price.toFixed(2)}` : t('Free') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="">
|
<div class="container mx-auto">
|
||||||
<div class="flex md:flex-row flex-col justify-between gap-8 my-6">
|
<div class="flex md:flex-row flex-col justify-between gap-8 my-6">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-8 flex items-center justify-center min-h-[300px] ">
|
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-8 flex items-center justify-center min-h-[300px] ">
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<p class="text-gray-600 dark:text-gray-300">
|
<p class="text-gray-600 dark:text-gray-300">
|
||||||
{{ productData.description }}
|
{{ productData.description }}
|
||||||
</p>
|
</p>
|
||||||
<div class="text-3xl font-bold text-(--text-sky-light) dark:text-(--text-sky-dark)">
|
<div class="text-3xl font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
||||||
{{ productData.price }}
|
{{ productData.price }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex md:flex-row flex-col justify-between md:items-end items-start gap-5 md:gap-0 md:mb-8 mb-4">
|
<div class="flex md:flex-row flex-col justify-between md:items-end items-start gap-5 md:gap-0 md:mb-8 mb-4">
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<span class="text-sm text-(--text-sky-light) dark:text-(--text-sky-dark) ">Colors:</span>
|
<span class="text-sm text-(--accent-blue-light) dark:text-(--accent-blue-dark) ">Colors:</span>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button v-for="color in productData.colors" :key="color.id" @click="selectedColor = color"
|
<button v-for="color in productData.colors" :key="color.id" @click="selectedColor = color"
|
||||||
class="w-10 h-10 border-2 transition-all" :class="selectedColor?.id === color.id
|
class="w-10 h-10 border-2 transition-all" :class="selectedColor?.id === color.id
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<suspense>
|
<suspense>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="">
|
<div class="container mx-auto">
|
||||||
<!-- <UNavigationMenu orientation="vertical" :items="listing" class="data-[orientation=vertical]:w-48">
|
<!-- <UNavigationMenu orientation="vertical" :items="listing" class="data-[orientation=vertical]:w-48">
|
||||||
<template #item="{ item, active }">
|
<template #item="{ item, active }">
|
||||||
<div class="flex items-center gap-2 px-3 py-2">
|
<div class="flex items-center gap-2 px-3 py-2">
|
||||||
@@ -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 CategoryMenu from '../inner/CategoryMenu.vue'
|
import CategoryMenu from '../inner/categoryMenu.vue'
|
||||||
|
|
||||||
interface Product {
|
interface Product {
|
||||||
reference: number
|
reference: number
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="">
|
<div class="container mx-auto">
|
||||||
<div class="flex flex-col gap-5 mb-6">
|
<div class="flex flex-col gap-5 mb-6">
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Customer Data') }}</h1>
|
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Customer Data') }}</h1>
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-4">
|
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-4">
|
||||||
<h2 class="text-xl font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
<h2 class="text-xl font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
||||||
<UIcon name="mdi:domain"
|
<UIcon name="mdi:domain"
|
||||||
class="text-[24px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
class="text-[24px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
{{ t('Company Information') }}
|
{{ t('Company Information') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 gap-10">
|
<div class="grid grid-cols-1 gap-10">
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
<h2
|
<h2
|
||||||
class="text-xl font-semibold text-black dark:text-white mb-2 flex items-center gap-2">
|
class="text-xl font-semibold text-black dark:text-white mb-2 flex items-center gap-2">
|
||||||
<UIcon name="mdi:map-marker"
|
<UIcon name="mdi:map-marker"
|
||||||
class="text-[24px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
class="text-[24px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
{{ t('Addresses') }}
|
{{ t('Addresses') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<UButton color="primary" variant="outline"
|
<UButton color="primary" variant="outline"
|
||||||
class="text-(--text-sky-light) dark:text-(--text-sky-dark) border-(--accent-blue-light) dark:border-(--accent-blue-dark)"
|
class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) border-(--accent-blue-light) dark:border-(--accent-blue-dark)"
|
||||||
@click="goToCreateAccount">
|
@click="goToCreateAccount">
|
||||||
<UIcon name="ic:sharp-edit" />
|
<UIcon name="ic:sharp-edit" />
|
||||||
{{ t('Edit Account') }}
|
{{ t('Edit Account') }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="">
|
<div class="container mx-auto">
|
||||||
<div class="max-w-2xl mx-auto">
|
<div class="max-w-2xl mx-auto">
|
||||||
<div class="flex flex-col gap-5 mb-6">
|
<div class="flex flex-col gap-5 mb-6">
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Create Account') }}</h1>
|
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Create Account') }}</h1>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<h2
|
<h2
|
||||||
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
||||||
<UIcon name="mdi:domain"
|
<UIcon name="mdi:domain"
|
||||||
class="text-[20px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
{{ t('Company Information') }}
|
{{ t('Company Information') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
<h2
|
<h2
|
||||||
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
||||||
<UIcon name="mdi:map-marker"
|
<UIcon name="mdi:map-marker"
|
||||||
class="text-[20px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
{{ t('Select Addresses') }}
|
{{ t('Select Addresses') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="bg-(--second-light) dark:bg-(--main-dark)">
|
<div class="bg-(--second-light) dark:bg-(--main-dark)">
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
||||||
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
||||||
<input type="radio" :value="address.id" v-model="selectedAddress"
|
<input type="radio" :value="address.id" v-model="selectedAddress"
|
||||||
class="mt-1 w-4 h-4 text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
class="mt-1 w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-black dark:text-white font-medium">{{ address.street }}
|
<p class="text-black dark:text-white font-medium">{{ address.street }}
|
||||||
</p>
|
</p>
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
||||||
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
||||||
<RouterLink :to="{ name: 'addresses' }"
|
<RouterLink :to="{ name: 'addresses' }"
|
||||||
class="inline-block mt-2 text-(--text-sky-light) dark:text-(--text-sky-dark) hover:underline">
|
class="inline-block mt-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
|
||||||
{{ t('Add Address') }}
|
{{ t('Add Address') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
<template>
|
|
||||||
<USelectMenu v-model="country" :items="countries" value-key="id"
|
|
||||||
class="w-48 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm" :searchInput="false">
|
|
||||||
<template #default>
|
|
||||||
<div class="flex flex-col items-start leading-tight">
|
|
||||||
<span class="text-xs text-gray-400">
|
|
||||||
Country/Currency
|
|
||||||
</span>
|
|
||||||
<span v-if="country" class="font-medium dark:text-white text-black">
|
|
||||||
{{ country?.name || 'Country' }} / {{ country?.ps_currency || 'Currency' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #item-leading="{ item }">
|
|
||||||
<div class="flex items-center gap-2 cursor-pointer">
|
|
||||||
<span class="text-lg">{{ item.flag }}</span>
|
|
||||||
<span class="font-medium dark:text-white text-black">
|
|
||||||
{{ item.name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { countries, currentCountry, switchLocalization } from '@/router/langs'
|
|
||||||
import { useCookie } from '@/composable/useCookie'
|
|
||||||
import { computed, watch } from 'vue'
|
|
||||||
|
|
||||||
const cookie = useCookie()
|
|
||||||
const country = computed({
|
|
||||||
get() {
|
|
||||||
return currentCountry.value
|
|
||||||
},
|
|
||||||
set(value: string) {
|
|
||||||
currentCountry.value = countries.find((x) => x.id == Number(value))
|
|
||||||
cookie.setCookie('country_id', `${countries.find((x) => x.id == Number(value))?.id}`, { days: 60, secure: true, sameSite: 'Lax' })
|
|
||||||
|
|
||||||
switchLocalization()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => country,
|
|
||||||
(newCountry) => {
|
|
||||||
if (newCountry) {
|
|
||||||
currentCountry.value = countries.find((x) => x.id == Number(newCountry.value?.id))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
<template>
|
|
||||||
<UEditor v-slot="{ editor }" v-model="localValue" content-type="html"
|
|
||||||
:ui="{ base: 'p-8 sm:px-16', root: 'p-2' }"
|
|
||||||
class="min-w-full border rounded-md bg-white! border-(--border-light)" placeholder="Write there ...">
|
|
||||||
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8 flex-wrap!">
|
|
||||||
<template #link>
|
|
||||||
<EditorLinkPopover :editor="editor" auto-open />
|
|
||||||
</template>
|
|
||||||
</UEditorToolbar>
|
|
||||||
</UEditor>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { EditorToolbarItem } from '@nuxt/ui';
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
modelValue: string
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'update:modelValue', value: string): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const localValue = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: (value: string) => emit('update:modelValue', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const toolbarItems: EditorToolbarItem[][] = [
|
|
||||||
// History controls
|
|
||||||
[{
|
|
||||||
kind: 'undo',
|
|
||||||
icon: 'i-lucide-undo',
|
|
||||||
tooltip: { text: 'Undo' }
|
|
||||||
}, {
|
|
||||||
kind: 'redo',
|
|
||||||
icon: 'i-lucide-redo',
|
|
||||||
tooltip: { text: 'Redo' }
|
|
||||||
}],
|
|
||||||
// Block types
|
|
||||||
[{
|
|
||||||
icon: 'i-lucide-heading',
|
|
||||||
tooltip: { text: 'Headings' },
|
|
||||||
content: {
|
|
||||||
align: 'start'
|
|
||||||
},
|
|
||||||
items: [{
|
|
||||||
kind: 'heading',
|
|
||||||
level: 1,
|
|
||||||
icon: 'i-lucide-heading-1',
|
|
||||||
label: 'Heading 1'
|
|
||||||
}, {
|
|
||||||
kind: 'heading',
|
|
||||||
level: 2,
|
|
||||||
icon: 'i-lucide-heading-2',
|
|
||||||
label: 'Heading 2'
|
|
||||||
}, {
|
|
||||||
kind: 'heading',
|
|
||||||
level: 3,
|
|
||||||
icon: 'i-lucide-heading-3',
|
|
||||||
label: 'Heading 3'
|
|
||||||
}, {
|
|
||||||
kind: 'heading',
|
|
||||||
level: 4,
|
|
||||||
icon: 'i-lucide-heading-4',
|
|
||||||
label: 'Heading 4'
|
|
||||||
}]
|
|
||||||
}, {
|
|
||||||
icon: 'i-lucide-list',
|
|
||||||
tooltip: { text: 'Lists' },
|
|
||||||
content: {
|
|
||||||
align: 'start'
|
|
||||||
},
|
|
||||||
items: [{
|
|
||||||
kind: 'bulletList',
|
|
||||||
icon: 'i-lucide-list',
|
|
||||||
label: 'Bullet List'
|
|
||||||
}, {
|
|
||||||
kind: 'orderedList',
|
|
||||||
icon: 'i-lucide-list-ordered',
|
|
||||||
label: 'Ordered List'
|
|
||||||
}]
|
|
||||||
}, {
|
|
||||||
kind: 'blockquote',
|
|
||||||
icon: 'i-lucide-text-quote',
|
|
||||||
tooltip: { text: 'Blockquote' }
|
|
||||||
}, {
|
|
||||||
kind: 'codeBlock',
|
|
||||||
icon: 'i-lucide-square-code',
|
|
||||||
tooltip: { text: 'Code Block' }
|
|
||||||
}, {
|
|
||||||
kind: 'horizontalRule',
|
|
||||||
icon: 'i-lucide-separator-horizontal',
|
|
||||||
tooltip: { text: 'Horizontal Rule' }
|
|
||||||
}],
|
|
||||||
// Text formatting
|
|
||||||
[{
|
|
||||||
kind: 'mark',
|
|
||||||
mark: 'bold',
|
|
||||||
icon: 'i-lucide-bold',
|
|
||||||
tooltip: { text: 'Bold' }
|
|
||||||
}, {
|
|
||||||
kind: 'mark',
|
|
||||||
mark: 'italic',
|
|
||||||
icon: 'i-lucide-italic',
|
|
||||||
tooltip: { text: 'Italic' }
|
|
||||||
}, {
|
|
||||||
kind: 'mark',
|
|
||||||
mark: 'underline',
|
|
||||||
icon: 'i-lucide-underline',
|
|
||||||
tooltip: { text: 'Underline' }
|
|
||||||
}, {
|
|
||||||
kind: 'mark',
|
|
||||||
mark: 'strike',
|
|
||||||
icon: 'i-lucide-strikethrough',
|
|
||||||
tooltip: { text: 'Strikethrough' }
|
|
||||||
}, {
|
|
||||||
kind: 'mark',
|
|
||||||
mark: 'code',
|
|
||||||
icon: 'i-lucide-code',
|
|
||||||
tooltip: { text: 'Code' }
|
|
||||||
}],
|
|
||||||
// Link
|
|
||||||
[{
|
|
||||||
kind: 'link',
|
|
||||||
icon: 'i-lucide-link',
|
|
||||||
tooltip: { text: 'Link' }
|
|
||||||
}],
|
|
||||||
// Text alignment
|
|
||||||
[{
|
|
||||||
icon: 'i-lucide-align-justify',
|
|
||||||
tooltip: { text: 'Text Align' },
|
|
||||||
content: {
|
|
||||||
align: 'end'
|
|
||||||
},
|
|
||||||
items: [{
|
|
||||||
kind: 'textAlign',
|
|
||||||
align: 'left',
|
|
||||||
icon: 'i-lucide-align-left',
|
|
||||||
label: 'Align Left'
|
|
||||||
}, {
|
|
||||||
kind: 'textAlign',
|
|
||||||
align: 'center',
|
|
||||||
icon: 'i-lucide-align-center',
|
|
||||||
label: 'Align Center'
|
|
||||||
}, {
|
|
||||||
kind: 'textAlign',
|
|
||||||
align: 'right',
|
|
||||||
icon: 'i-lucide-align-right',
|
|
||||||
label: 'Align Right'
|
|
||||||
}, {
|
|
||||||
kind: 'textAlign',
|
|
||||||
align: 'justify',
|
|
||||||
icon: 'i-lucide-align-justify',
|
|
||||||
label: 'Align Justify'
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
</script>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<template>
|
|
||||||
<UButton variant="outline" size="sm" color="info" @click="themeStorage.setTheme()">
|
|
||||||
<span class="hidden sm:inline">
|
|
||||||
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
|
||||||
</span>
|
|
||||||
</UButton>
|
|
||||||
<!-- <UButton variant="solid" size="sm" color="info" @click="themeStorage.setTheme()">
|
|
||||||
<span class="hidden sm:inline">
|
|
||||||
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
|
||||||
</span>
|
|
||||||
</UButton>
|
|
||||||
<UButton variant="soft" size="sm" color="info" @click="themeStorage.setTheme()">
|
|
||||||
<span class="hidden sm:inline">
|
|
||||||
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
|
||||||
</span>
|
|
||||||
</UButton>
|
|
||||||
<UButton variant="subtle" size="sm" color="info" @click="themeStorage.setTheme()">
|
|
||||||
<span class="hidden sm:inline">
|
|
||||||
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
|
||||||
</span>
|
|
||||||
</UButton>
|
|
||||||
<UButton variant="ghost" size="sm" color="info" @click="themeStorage.setTheme()">
|
|
||||||
<span class="hidden sm:inline">
|
|
||||||
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
|
||||||
</span>
|
|
||||||
</UButton>
|
|
||||||
<UButton variant="link" size="sm" color="info" @click="themeStorage.setTheme()">
|
|
||||||
<span class="hidden sm:inline">
|
|
||||||
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
|
||||||
</span>
|
|
||||||
</UButton> -->
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useThemeStore } from '@/stores/theme'
|
|
||||||
const themeStorage = useThemeStore()
|
|
||||||
</script>
|
|
||||||
@@ -39,7 +39,7 @@ function adaptMenu(menu: NavigationMenuItem[]) {
|
|||||||
adaptMenu(item.children);
|
adaptMenu(item.children);
|
||||||
item.children.unshift({
|
item.children.unshift({
|
||||||
label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: {
|
label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: {
|
||||||
name: 'admin-products-category', params: {
|
name: 'customer-products-category', params: {
|
||||||
category_id: item.params.category_id,
|
category_id: item.params.category_id,
|
||||||
link_rewrite: item.params.link_rewrite
|
link_rewrite: item.params.link_rewrite
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ function adaptMenu(menu: NavigationMenuItem[]) {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
item.to = {
|
item.to = {
|
||||||
name: 'admin-products-category', params: {
|
name: 'customer-products-category', params: {
|
||||||
category_id: item.params.category_id,
|
category_id: item.params.category_id,
|
||||||
link_rewrite: item.params.link_rewrite
|
link_rewrite: item.params.link_rewrite
|
||||||
}
|
}
|
||||||
@@ -1,47 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<USelectMenu v-model="locale" :items="langs" value-key="iso_code"
|
<USelectMenu v-model="locale" :items="langs"
|
||||||
class="w-48 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm" :searchInput="false">
|
class="w-40 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm hover:none!" valueKey="iso_code"
|
||||||
<template #default>
|
:searchInput="false">
|
||||||
<div class="flex items-center gap-2">
|
<template #default="{ modelValue }">
|
||||||
<!-- <span class="text-lg">{{ selectedLang?.flag }}</span> -->
|
<div class="flex items-center gap-1">
|
||||||
|
<!-- <span class="text-md dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.flag}}</span> -->
|
||||||
<div class="flex flex-col leading-tight items-start">
|
<span class="font-medium dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.name}}</span>
|
||||||
<span class="text-xs text-gray-400">
|
|
||||||
Language
|
|
||||||
</span>
|
|
||||||
<span class="font-medium dark:text-white text-black">
|
|
||||||
{{ selectedLang?.name || 'Select language' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item-leading="{ item }">
|
<template #item-leading="{ item }">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center rounded-md cursor-pointer transition-colors">
|
||||||
<span class="text-lg">{{ item.flag }}</span>
|
<!-- <span class="text-md ">{{ item.flag }}</span> -->
|
||||||
<span class="font-medium dark:text-white text-black">
|
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
||||||
{{ item.name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { langs, currentLang, switchLocalization } from '@/router/langs'
|
import { langs, currentLang } from '@/router/langs'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useCookie } from '@/composable/useCookie'
|
import { useCookie } from '@/composable/useCookie'
|
||||||
import { computed, watch } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const selectedLang = computed(() =>
|
|
||||||
langs.find(item => item.iso_code === locale.value)
|
|
||||||
)
|
|
||||||
|
|
||||||
const cookie = useCookie()
|
const cookie = useCookie()
|
||||||
|
|
||||||
const locale = computed({
|
const locale = computed({
|
||||||
get() {
|
get() {
|
||||||
return currentLang.value?.iso_code || i18n.locale.value
|
return currentLang.value?.iso_code || i18n.locale.value
|
||||||
@@ -65,10 +52,21 @@ const locale = computed({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switchLocalization()
|
changeLang()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function changeLang() {
|
||||||
|
try {
|
||||||
|
const { items } = await useFetchJson('/api/v1/public/auth/update-choice', {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.params.locale,
|
() => route.params.locale,
|
||||||
(newLocale) => {
|
(newLocale) => {
|
||||||
12
bo/src/components/inner/themeSwitch.vue
Normal file
12
bo/src/components/inner/themeSwitch.vue
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<UButton variant="ghost" size="sm" @click="themeStorage.setTheme()">
|
||||||
|
<span class="hidden sm:inline">
|
||||||
|
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
||||||
|
</span>
|
||||||
|
</UButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useThemeStore } from '@/stores/theme'
|
||||||
|
const themeStorage = useThemeStore()
|
||||||
|
</script>
|
||||||
@@ -1,295 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import TopBar from '@/components/TopBar.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-1 overflow-x-hidden h-svh">
|
<div class="h-screen grid grid-rows-[auto_1fr_auto]">
|
||||||
<USidebar v-model:open="open" collapsible="icon" rail :ui="{
|
<main class="pt-20 pb-10">
|
||||||
container: 'h-full z-80',
|
<TopBar />
|
||||||
inner: 'bg-elevated/25 divide-transparent',
|
<div class="container mx-auto px-4">
|
||||||
body: 'py-0'
|
|
||||||
}">
|
|
||||||
<template #header>
|
|
||||||
<UDropdownMenu :items="teamsItems" :content="{ align: 'start', collisionPadding: 12 }"
|
|
||||||
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }">
|
|
||||||
<UButton v-bind="selectedTeam" trailing-icon="i-lucide-chevrons-up-down" color="neutral" variant="ghost"
|
|
||||||
square class="w-full data-[state=open]:bg-elevated overflow-hidden" :ui="{
|
|
||||||
trailingIcon: 'text-dimmed ms-auto'
|
|
||||||
}" />
|
|
||||||
</UDropdownMenu>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #default="{ state }">
|
|
||||||
<UNavigationMenu :key="state" :items="menuItems" orientation="vertical"
|
|
||||||
:ui="{ link: 'p-1.5 overflow-hidden' }" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<UDropdownMenu :items="userItems" :content="{ align: 'center', collisionPadding: 12 }"
|
|
||||||
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }">
|
|
||||||
<UButton v-bind="user" :label="user?.name" trailing-icon="i-lucide-chevrons-up-down" color="neutral"
|
|
||||||
variant="ghost" square class="w-full data-[state=open]:bg-elevated overflow-hidden" :ui="{
|
|
||||||
trailingIcon: 'text-dimmed ms-auto'
|
|
||||||
}" />
|
|
||||||
</UDropdownMenu>
|
|
||||||
</template>
|
|
||||||
</USidebar>
|
|
||||||
|
|
||||||
<div class="flex-1 flex flex-col">
|
|
||||||
<div class="flex h-(--ui-header-height) shrink-0 items-center justify-between px-4 border-b border-default">
|
|
||||||
<UButton icon="i-lucide-panel-left" color="neutral" variant="ghost" aria-label="Toggle sidebar"
|
|
||||||
@click="open = !open" />
|
|
||||||
<div class="hidden md:flex items-center gap-12">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<CountryCurrencySwitch />
|
|
||||||
<LangSwitch />
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<ThemeSwitch />
|
|
||||||
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
|
|
||||||
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark) whitespace-nowrap">
|
|
||||||
{{ $t('general.logout') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-1 p-4 bg-slate-50">
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, onMounted } from 'vue'
|
|
||||||
import { useColorMode } from '@vueuse/core'
|
|
||||||
import type { DropdownMenuItem, NavigationMenuItem } from '@nuxt/ui'
|
|
||||||
import { defineShortcuts, extractShortcuts } from '@nuxt/ui/runtime/composables/defineShortcuts.js'
|
|
||||||
import { LabelTrans, TopMenuItem } from '@/types'
|
|
||||||
|
|
||||||
const open = ref(true)
|
|
||||||
const authStore = useAuthStore()
|
|
||||||
const colorMode = useColorMode()
|
|
||||||
|
|
||||||
const teams = ref([
|
|
||||||
{
|
|
||||||
label: 'Nuxt',
|
|
||||||
avatar: {
|
|
||||||
src: 'https://github.com/nuxt.png',
|
|
||||||
alt: 'Nuxt'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Vue',
|
|
||||||
avatar: {
|
|
||||||
src: 'https://github.com/vuejs.png',
|
|
||||||
alt: 'Vue'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'UnJS',
|
|
||||||
avatar: {
|
|
||||||
src: 'https://github.com/unjs.png',
|
|
||||||
alt: 'UnJS'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
const selectedTeam = ref(teams.value[0])
|
|
||||||
|
|
||||||
const teamsItems = computed<DropdownMenuItem[][]>(() => {
|
|
||||||
return [
|
|
||||||
teams.value.map((team, index) => ({
|
|
||||||
...team,
|
|
||||||
kbds: ['meta', String(index + 1)],
|
|
||||||
onSelect() {
|
|
||||||
selectedTeam.value = team
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
label: 'Create team',
|
|
||||||
icon: 'i-lucide-circle-plus'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
function getItems(state: 'collapsed' | 'expanded') {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: 'Inbox',
|
|
||||||
icon: 'i-lucide-inbox',
|
|
||||||
badge: '4'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Issues',
|
|
||||||
icon: 'i-lucide-square-dot'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Activity',
|
|
||||||
icon: 'i-lucide-square-activity'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Settings',
|
|
||||||
icon: 'i-lucide-settings',
|
|
||||||
defaultOpen: true,
|
|
||||||
children:
|
|
||||||
state === 'expanded'
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: 'General',
|
|
||||||
icon: 'i-lucide-house'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Team',
|
|
||||||
icon: 'i-lucide-users'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Billing',
|
|
||||||
icon: 'i-lucide-credit-card'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: []
|
|
||||||
}
|
|
||||||
] satisfies NavigationMenuItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { currentLang } from '@/router/langs'
|
|
||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
|
||||||
import { useAuthStore } from '@/stores/auth'
|
|
||||||
import CountryCurrencySwitch from '@/components/inner/CountryCurrencySwitch.vue'
|
|
||||||
import LangSwitch from '@/components/inner/LangSwitch.vue'
|
|
||||||
import ThemeSwitch from '@/components/inner/ThemeSwitch.vue'
|
|
||||||
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const menu = ref<TopMenuItem[] | null>(null)
|
|
||||||
|
|
||||||
async function getTopMenu() {
|
|
||||||
try {
|
|
||||||
const { items } = await useFetchJson<TopMenuItem[]>('/api/v1/restricted/menu/get-top-menu')
|
|
||||||
|
|
||||||
menu.value = items
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getTopMenu()
|
|
||||||
})
|
|
||||||
|
|
||||||
const menuItems = computed(() => {
|
|
||||||
if (!menu.value?.length) return []
|
|
||||||
|
|
||||||
return transformMenu(
|
|
||||||
menu.value || [],
|
|
||||||
currentLang.value?.iso_code
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
function transformMenu(
|
|
||||||
items: TopMenuItem[],
|
|
||||||
locale: string | undefined
|
|
||||||
): NavigationMenuItem[] {
|
|
||||||
return items.map((item) => {
|
|
||||||
const route: NavigationMenuItem = {
|
|
||||||
icon: item.label.icon || 'i-lucide-house',
|
|
||||||
label:
|
|
||||||
item.label.trans?.[locale as keyof LabelTrans]?.label ||
|
|
||||||
item.label.trans?.en?.label ||
|
|
||||||
'—',
|
|
||||||
children: item.children
|
|
||||||
? transformMenu(item.children, locale)
|
|
||||||
: undefined,
|
|
||||||
onSelect: () => {
|
|
||||||
router.push({
|
|
||||||
name: item.params.route.name,
|
|
||||||
params: {
|
|
||||||
...(item.params.route.params || {}),
|
|
||||||
locale: currentLang.value?.iso_code
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return route
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = ref({
|
|
||||||
name: 'Benjamin Canac',
|
|
||||||
avatar: {
|
|
||||||
src: 'https://github.com/benjamincanac.png',
|
|
||||||
alt: 'Benjamin Canac'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const userItems = computed<DropdownMenuItem[][]>(() => [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
label: 'Profile',
|
|
||||||
icon: 'i-lucide-user'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Billing',
|
|
||||||
icon: 'i-lucide-credit-card'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Settings',
|
|
||||||
icon: 'i-lucide-settings',
|
|
||||||
to: '/settings'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
label: 'Appearance',
|
|
||||||
icon: 'i-lucide-sun-moon',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: 'Light',
|
|
||||||
icon: 'i-lucide-sun',
|
|
||||||
type: 'checkbox',
|
|
||||||
checked: colorMode.value === 'light',
|
|
||||||
onUpdateChecked(checked: boolean) {
|
|
||||||
if (checked) {
|
|
||||||
colorMode.preference = 'light'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSelect(e: Event) {
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Dark',
|
|
||||||
icon: 'i-lucide-moon',
|
|
||||||
type: 'checkbox',
|
|
||||||
checked: colorMode.value === 'dark',
|
|
||||||
onUpdateChecked(checked: boolean) {
|
|
||||||
if (checked) {
|
|
||||||
colorMode.preference = 'dark'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSelect(e: Event) {
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
label: 'GitHub',
|
|
||||||
icon: 'i-simple-icons-github',
|
|
||||||
to: 'https://github.com/nuxt/ui',
|
|
||||||
target: '_blank'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Log out',
|
|
||||||
icon: 'i-lucide-log-out'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
])
|
|
||||||
|
|
||||||
defineShortcuts(extractShortcuts(teamsItems.value))
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
|
|
||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
import { initCountryCurrency, initLangs, langs } from '@/router/langs'
|
import { initLangs, langs } from '@/router/langs'
|
||||||
import { watch } from 'vue'
|
import { watch } from 'vue'
|
||||||
import { createI18n, type PathValue } from 'vue-i18n'
|
import { createI18n, type PathValue } from 'vue-i18n'
|
||||||
|
|
||||||
// const x =
|
// const x =
|
||||||
await initLangs()
|
await initLangs()
|
||||||
await initCountryCurrency()
|
|
||||||
export const i18ninstall = createI18n({
|
export const i18ninstall = createI18n({
|
||||||
legacy: false, // you must set `false`, to use Composition API
|
legacy: false, // you must set `false`, to use Composition API
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { currentLang, langs, switchLocalization } from './langs'
|
import { currentLang, langs } from './langs'
|
||||||
import { getSettings } from './settings'
|
import { getSettings } from './settings'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { getRoutes } from './menu'
|
import { getRoutes } from './menu'
|
||||||
@@ -8,7 +8,6 @@ function isAuthenticated(): boolean {
|
|||||||
if (typeof document === 'undefined') return false
|
if (typeof document === 'undefined') return false
|
||||||
return document.cookie.split('; ').some((c) => c === 'is_authenticated=1')
|
return document.cookie.split('; ').some((c) => c === 'is_authenticated=1')
|
||||||
}
|
}
|
||||||
await switchLocalization()
|
|
||||||
await getSettings()
|
await getSettings()
|
||||||
|
|
||||||
const routes = await getRoutes()
|
const routes = await getRoutes()
|
||||||
@@ -81,8 +80,6 @@ async function setRoutes() {
|
|||||||
meta: item.meta ? JSON.parse(item.meta) : {}
|
meta: item.meta ? JSON.parse(item.meta) : {}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// await router.replace(router.currentRoute.value.fullPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await setRoutes()
|
await setRoutes()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user