47 Commits

Author SHA1 Message Date
Daniel Goc
1083ab7a61 added addresses endpoints 2026-04-09 12:21:56 +02:00
75997ab15b Merge pull request 'storage' (#46) from storage into main
Reviewed-on: #46
Reviewed-by: Wiktor Dudzic <dudzic_wiktor@ma-al.com>
2026-04-08 13:58:22 +00:00
Daniel Goc
569a805a13 small fix 2026-04-08 13:23:05 +02:00
Daniel Goc
578d8c6cac merged with current main 2026-04-08 13:20:07 +02:00
Daniel Goc
cbd0baaa50 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into storage 2026-04-08 13:19:45 +02:00
Daniel Goc
7eee0bd032 rebuilt storage 2026-04-08 13:09:19 +02:00
92ba9c5f07 Merge pull request 'feat: searching on customer list' (#54) from product-procedures into main
Reviewed-on: #54
2026-04-08 07:58:55 +00:00
a121ddc246 Merge branch 'main' into product-procedures 2026-04-07 14:00:27 +00:00
d56650ae5d feat: searching on customer list 2026-04-07 14:42:56 +02:00
1a6311dc3d Merge pull request 'fix: google provider auth' (#53) from product-procedures into main
Reviewed-on: #53
2026-04-07 11:39:08 +00:00
2e645f3368 fix: google provider auth 2026-04-07 13:36:43 +02:00
de3f2d1777 Merge pull request 'product-procedures' (#52) from product-procedures into main
Reviewed-on: #52
Reviewed-by: Marek Goc <goc_marek@ma-al.com>
2026-04-07 08:45:15 +00:00
9187297367 refactor: move lists to their representative repos 2026-04-07 10:32:30 +02:00
813d1f4879 feat: add customer list, modify pagination utils 2026-04-07 09:28:39 +02:00
c5cc4f7a48 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into product-procedures 2026-04-03 15:58:40 +02:00
76ca2a2eed chore: adapt code to new teleport feature 2026-04-03 15:58:35 +02:00
84388792f0 Merge pull request 'sanitize and save URL slugs' (#51) from translate_new_field into main
Reviewed-on: #51
Reviewed-by: Arina Yakovenko <yakovenko_arina@ma-al.com>
2026-04-03 13:15:32 +00:00
Daniel Goc
7264a11ba6 sanitize and save URL slugs 2026-04-03 14:58:50 +02:00
61dc240c38 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into product-procedures 2026-04-03 14:37:58 +02:00
Daniel Goc
f6b321b602 a few fixes for user teleportation 2026-04-03 13:55:57 +02:00
Daniel Goc
af91842b14 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into storage 2026-04-03 13:29:06 +02:00
04e238fd66 Merge pull request 'user_teleport' (#50) from user_teleport into main
Reviewed-on: #50
Reviewed-by: Wiktor Dudzic <dudzic_wiktor@ma-al.com>
2026-04-03 11:27:11 +00:00
Daniel Goc
e0c53c97ba Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into user_teleport 2026-04-03 13:01:37 +02:00
09a77c14c9 Merge pull request 'add image link (large_default) to product description' (#49) from add_image_link into main
Reviewed-on: #49
Reviewed-by: Wiktor Dudzic <dudzic_wiktor@ma-al.com>
2026-04-03 10:58:21 +00:00
Daniel Goc
c7533a8deb add image link (large_default) to product description 2026-04-03 12:24:05 +02:00
Daniel Goc
1bab7f642f typo 2026-04-03 11:44:15 +02:00
Daniel Goc
a988bbbc33 added copying and moving 2026-04-03 11:25:16 +02:00
701004d005 chore: add bruno endpoints 2026-04-03 09:40:30 +02:00
c31964c41b Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into product-procedures 2026-04-03 08:42:56 +02:00
0ed9d792b6 feat: roles, permissions 2026-04-02 15:06:00 +02:00
Daniel Goc
395d670298 add storage to .gitignore 2026-04-02 14:00:58 +02:00
Daniel Goc
7d4242abb1 move path to params 2026-04-02 13:52:50 +02:00
Daniel Goc
9c7eb5ee4e Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into storage 2026-04-02 11:31:39 +02:00
Daniel Goc
833f4a5a07 deleting and uploading files 2026-04-02 11:26:58 +02:00
Daniel Goc
b9bc121d43 getting to upload 2026-04-02 10:27:14 +02:00
Daniel Goc
b2acb8c922 storage 2026-04-01 13:30:54 +02:00
Daniel Goc
03f04b2f53 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into user_teleport 2026-03-31 16:57:44 +02:00
Daniel Goc
55da953f32 add teleporting 2026-03-31 16:56:05 +02:00
6428ddb527 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into product-procedures 2026-03-31 13:42:27 +02:00
05bfa6e8b8 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into product-procedures 2026-03-30 15:17:53 +02:00
f4ad8e02b4 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into product-procedures 2026-03-27 08:47:41 +01:00
bd97ed1a3b feat: creat main products query 2026-03-26 15:59:13 +01:00
df14eb5ae4 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into product-procedures 2026-03-26 15:57:21 +01:00
f5d524d45b Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into product-procedures 2026-03-26 11:46:37 +01:00
78bdac8ff0 Merge branch 'product-procedures' of ssh://git.ma-al.com:8822/goc_daniel/b2b into product-procedures 2026-03-26 10:07:15 +01:00
2c128a4b36 feat: create procedure for retrieving products 2026-03-25 08:38:05 +01:00
dd806bbb1e feat: create procedure for retrieving products 2026-03-19 15:44:42 +01:00
132 changed files with 4025 additions and 412 deletions

4
.env
View File

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

4
.gitignore vendored
View File

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

14
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "./app/cmd/main.go",
"cwd": "${workspaceFolder}",
"envFile": "${workspaceFolder}/.env"
}
]
}

View File

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

View File

@@ -1,12 +1,16 @@
package middleware
import (
"encoding/base64"
"strconv"
"strings"
"time"
"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/authService"
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"
)
@@ -60,9 +64,52 @@ func AuthMiddleware() fiber.Handler {
})
}
// Set user in context
c.Locals(constdata.USER_LOCALES_NAME, user.ToSession())
c.Locals(constdata.USER_LOCALES_ID, user.ID)
// 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 model.CustomerRole(user.Role.Name) != model.RoleAdmin {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "admin access required",
})
}
targetUserID, err := strconv.Atoi(targetUserIDAttribute)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "invalid target user id attribute",
})
}
// to verify target user, we use the same functionality as for verifying original user
// Get target user from database
user, err = authService.GetUserByID(uint(targetUserID))
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "target user not found",
})
}
// Check if target user is active
if !user.IsActive {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "target user account is inactive",
})
}
userLocale.User = user
c.Locals(constdata.USER_LOCALE, &userLocale)
return c.Next()
}
@@ -71,21 +118,14 @@ func AuthMiddleware() fiber.Handler {
// RequireAdmin creates admin-only middleware
func RequireAdmin() fiber.Handler {
return func(c fiber.Ctx) error {
user := c.Locals("user")
if user == nil {
originalUserRole, ok := localeExtractor.GetOriginalUserRole(c)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "not authenticated",
})
}
userSession, ok := user.(*model.UserSession)
if !ok {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "invalid user session",
})
}
if userSession.Role != model.RoleAdmin {
if model.CustomerRole(originalUserRole.Name) != model.RoleAdmin {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "admin access required",
})
@@ -95,22 +135,70 @@ func RequireAdmin() fiber.Handler {
}
}
// GetUserID extracts user ID from context
func GetUserID(c fiber.Ctx) uint {
userID, ok := c.Locals("userID").(uint)
if !ok {
return 0
}
return userID
}
// Webdav
func Webdav() fiber.Handler {
authService := authService.NewAuthService()
// GetUser extracts user from context
func GetUser(c fiber.Ctx) *model.UserSession {
user, ok := c.Locals("user").(*model.UserSession)
if !ok {
return nil
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 {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "user not found",
})
}
if user.WebdavExpires != nil && user.WebdavExpires.Before(time.Now()) {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "invalid or expired token",
})
}
var userLocale model.UserLocale
userLocale.OriginalUser = user
userLocale.User = user
c.Locals(constdata.USER_LOCALE, &userLocale)
return c.Next()
}
return user
}
// GetConfig returns the app config

View File

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

View File

@@ -0,0 +1,28 @@
package middleware
import (
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
"git.ma-al.com/goc_daniel/b2b/app/model"
"github.com/gofiber/fiber/v3"
)
func Require(p perms.Permission) fiber.Handler {
return func(c fiber.Ctx) error {
u := c.Locals("user")
if u == nil {
return c.SendStatus(fiber.StatusUnauthorized)
}
user, ok := u.(*model.UserSession)
if !ok {
return c.SendStatus(fiber.StatusInternalServerError)
}
for _, perm := range user.Permissions {
if perm == p {
return c.Next()
}
}
return c.SendStatus(fiber.StatusForbidden)
}
}

View File

@@ -0,0 +1,10 @@
package perms
type Permission string
const (
UserReadAny Permission = "user.read.any"
UserWriteAny Permission = "user.write.any"
UserDeleteAny Permission = "user.delete.any"
CurrencyWrite Permission = "currency.write"
)

View File

@@ -268,15 +268,15 @@ func (h *AuthHandler) RefreshToken(c fiber.Ctx) error {
// Me returns the current user info
func (h *AuthHandler) Me(c fiber.Ctx) error {
user := c.Locals("user")
if user == nil {
userLocale := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
if userLocale.OriginalUser == nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated),
})
}
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
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 {
return c.Status(fiber.StatusUnauthorized).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated)))
}
user := model.Customer{
ID: userLocals.UserID,
Email: userLocals.Email,
Role: userLocals.Role,
LangID: userLocals.LangID,
CountryID: userLocals.CountryID,
IsActive: userLocals.IsActive,
}
// Parse language and country_id from query params
langIDStr := c.Query("lang_id")
@@ -375,7 +366,7 @@ func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadLangID)))
}
user.LangID = uint(parsedID)
userLocale.OriginalUser.LangID = uint(parsedID)
}
countryIDStr := c.Query("country_id")
@@ -386,10 +377,10 @@ func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadCountryID)))
}
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 {
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{

View File

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

View File

@@ -0,0 +1,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_info, err := h.addressesService.RetrieveAddressesInfo(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_info, 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)))
}

View File

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

View File

@@ -0,0 +1,70 @@
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/currencyService"
"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 CurrencyHandler struct {
CurrencyService *currencyService.CurrencyService
config *config.Config
}
func NewCurrencyHandler() *CurrencyHandler {
currencyService := currencyService.New()
return &CurrencyHandler{
CurrencyService: currencyService,
config: config.Get(),
}
}
func CurrencyHandlerRoutes(r fiber.Router) fiber.Router {
handler := NewCurrencyHandler()
r.Post("/currency-rate", middleware.Require(perms.CurrencyWrite), handler.PostCurrencyRate)
r.Get("/currency-rate/:id", handler.GetCurrencyRate)
return r
}
func (h *CurrencyHandler) PostCurrencyRate(c fiber.Ctx) error {
var currencyRate model.CurrencyRate
if err := c.Bind().Body(&currencyRate); err != nil {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrJSONBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrJSONBody)))
}
err := h.CurrencyService.CreateCurrencyRate(&currencyRate)
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(""), 1, i18n.T_(c, response.Message_OK)))
}
func (h *CurrencyHandler) GetCurrencyRate(c fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.Atoi(idStr)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
currency, err := h.CurrencyService.GetCurrency(uint(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(currency, 0, i18n.T_(c, response.Message_OK)))
}

View File

@@ -0,0 +1,111 @@
package restricted
import (
"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/service/customerService"
"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 customerHandler struct {
service *customerService.CustomerService
}
func NewCustomerHandler() *customerHandler {
customerService := customerService.New()
return &customerHandler{
service: customerService,
}
}
func CustomerHandlerRoutes(r fiber.Router) fiber.Router {
handler := NewCustomerHandler()
r.Get("", handler.customerData)
r.Get("/list", handler.listCustomers)
return r
}
func (h *customerHandler) customerData(fc fiber.Ctx) error {
var customerId uint
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)))
}
customerIdStr := fc.Query("id")
if customerIdStr != "" {
id, err := strconv.ParseUint(customerIdStr, 10, 64)
if err != nil {
return fiber.ErrBadRequest
}
if user.ID != uint(id) && !user.HasPermission(perms.UserReadAny) {
return fc.Status(fiber.StatusForbidden).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrForbidden)))
}
customerId = uint(id)
} else {
customerId = user.ID
}
customer, err := h.service.GetById(customerId)
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)))
}
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)))
}
if !user.HasPermission(perms.UserReadAny) {
return fc.Status(fiber.StatusForbidden).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrForbidden)))
}
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")
if search != "" {
if !user.HasPermission(perms.UserReadAny) {
return fc.Status(fiber.StatusForbidden).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrForbidden)))
}
}
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",
}

View File

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

View File

@@ -1,10 +1,13 @@
package restricted
import (
"strconv"
"git.ma-al.com/goc_daniel/b2b/app/config"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/service/listService"
"git.ma-al.com/goc_daniel/b2b/app/service/productService"
"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"
@@ -12,50 +15,88 @@ import (
"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
type ProductsHandler struct {
productService *productService.ProductService
config *config.Config
}
// NewListHandler creates a new ListHandler instance
func NewListHandler() *ListHandler {
listService := listService.New()
return &ListHandler{
listService: listService,
config: config.Get(),
// NewListProductsHandler creates a new ListProductsHandler instance
func NewProductsHandler() *ProductsHandler {
productService := productService.New()
return &ProductsHandler{
productService: productService,
config: config.Get(),
}
}
func ListHandlerRoutes(r fiber.Router) fiber.Router {
handler := NewListHandler()
func ProductsHandlerRoutes(r fiber.Router) fiber.Router {
handler := NewProductsHandler()
r.Get("/list-products", handler.ListProducts)
r.Get("/list-users", handler.ListUsers)
r.Get("/:id/:country_id/:quantity", handler.GetProductJson)
r.Get("/list", handler.ListProducts)
return r
}
func (h *ListHandler) ListProducts(c fiber.Ctx) error {
func (h *ProductsHandler) GetProductJson(c fiber.Ctx) error {
idStr := c.Params("id")
p_id_product, err := strconv.Atoi(idStr)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
country_idStr := c.Params("country_id")
b2b_id_country, err := strconv.Atoi(country_idStr)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
quantityStr := c.Params("quantity")
p_quantity, err := strconv.Atoi(quantityStr)
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)))
}
productJson, err := h.productService.GetJSON(p_id_product, int(customer.LangID), int(customer.ID), b2b_id_country, p_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(&productJson, 1, i18n.T_(c, response.Message_OK)))
}
func (h *ProductsHandler) 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)
id_lang, ok := localeExtractor.GetLangID(c)
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)
list, err := h.productService.Find(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)))
return c.JSON(response.Make(&list.Items, int(list.Count), i18n.T_(c, response.Message_OK)))
}
var columnMappingListProducts map[string]string = map[string]string{
@@ -66,33 +107,3 @@ var columnMappingListProducts map[string]string = map[string]string{
"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",
}

View File

@@ -4,8 +4,10 @@ import (
"strconv"
"git.ma-al.com/goc_daniel/b2b/app/config"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/service/productTranslationService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
@@ -41,7 +43,7 @@ func ProductTranslationHandlerRoutes(r fiber.Router) fiber.Router {
// GetProductDescription returns the product description for a given product ID
func (h *ProductTranslationHandler) GetProductDescription(c fiber.Ctx) error {
userID, ok := c.Locals("userID").(uint)
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)))
@@ -72,12 +74,18 @@ func (h *ProductTranslationHandler) GetProductDescription(c fiber.Ctx) error {
// SaveProductDescription saves the description for a given product ID, in given language
func (h *ProductTranslationHandler) SaveProductDescription(c fiber.Ctx) error {
userID, ok := c.Locals("userID").(uint)
userID, ok := localeExtractor.GetUserID(c)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
}
userRole, ok := localeExtractor.GetOriginalUserRole(c)
if !ok || model.CustomerRole(userRole.Name) != model.RoleAdmin {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
}
productID_attribute := c.Query("productID")
productID, err := strconv.Atoi(productID_attribute)
if err != nil {
@@ -109,12 +117,18 @@ func (h *ProductTranslationHandler) SaveProductDescription(c fiber.Ctx) error {
// TranslateProductDescription returns translated product description
func (h *ProductTranslationHandler) TranslateProductDescription(c fiber.Ctx) error {
userID, ok := c.Locals("userID").(uint)
userID, ok := localeExtractor.GetUserID(c)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
}
userRole, ok := localeExtractor.GetOriginalUserRole(c)
if !ok || model.CustomerRole(userRole.Name) != model.RoleAdmin {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
}
productID_attribute := c.Query("productID")
productID, err := strconv.Atoi(productID_attribute)
if err != nil {

View File

@@ -4,9 +4,11 @@ import (
"encoding/json"
"fmt"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/service/meiliService"
searchservice "git.ma-al.com/goc_daniel/b2b/app/service/searchService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
@@ -36,12 +38,18 @@ func MeiliSearchHandlerRoutes(r fiber.Router) fiber.Router {
}
func (h *MeiliSearchHandler) CreateIndex(c fiber.Ctx) error {
id_lang, ok := c.Locals("langID").(uint)
id_lang, ok := localeExtractor.GetLangID(c)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
userRole, ok := localeExtractor.GetOriginalUserRole(c)
if !ok || model.CustomerRole(userRole.Name) != model.RoleAdmin {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
}
err := h.meiliService.CreateIndex(id_lang)
if err != nil {
fmt.Printf("CreateIndex error: %v\n", err)
@@ -49,12 +57,11 @@ func (h *MeiliSearchHandler) CreateIndex(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
nothing := ""
return c.JSON(response.Make(&nothing, 0, i18n.T_(c, response.Message_OK)))
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
}
func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
id_lang, ok := c.Locals("langID").(uint)
id_lang, ok := localeExtractor.GetLangID(c)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
@@ -88,7 +95,7 @@ func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
}
func (h *MeiliSearchHandler) GetSettings(c fiber.Ctx) error {
id_lang, ok := c.Locals("langID").(uint)
id_lang, ok := localeExtractor.GetLangID(c)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))

View File

@@ -0,0 +1,100 @@
package restricted
import (
"strconv"
"git.ma-al.com/goc_daniel/b2b/app/config"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/service/storageService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
"github.com/gofiber/fiber/v3"
)
type StorageHandler struct {
storageService *storageService.StorageService
config *config.Config
}
func NewStorageHandler() *StorageHandler {
return &StorageHandler{
storageService: storageService.New(),
config: config.Get(),
}
}
func StorageHandlerRoutes(r fiber.Router) fiber.Router {
handler := NewStorageHandler()
// for all users
r.Get("/list-content/*", handler.ListContent)
r.Get("/download-file/*", handler.DownloadFile)
// for admins only
r.Get("/create-new-webdav-token", handler.CreateNewWebdavToken)
return r
}
// accepted path looks like e.g. "/folder1/" or "folder1"
func (h *StorageHandler) ListContent(c fiber.Ctx) error {
// relative path defaults to root directory
abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
entries_in_list, err := h.storageService.ListContent(abs_path)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(entries_in_list, 0, i18n.T_(c, response.Message_OK)))
}
func (h *StorageHandler) DownloadFile(c fiber.Ctx) error {
abs_path, err := h.storageService.AbsPath(h.config.Storage.RootFolder, c.Params("*"))
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
f, filename, filesize, err := h.storageService.DownloadFilePrep(abs_path)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
c.Attachment(filename)
c.Set("Content-Length", strconv.FormatInt(filesize, 10))
c.Set("Content-Type", "application/octet-stream")
return c.SendStream(f, int(filesize))
}
func (h *StorageHandler) CreateNewWebdavToken(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
}
userRole, ok := localeExtractor.GetOriginalUserRole(c)
if !ok || model.CustomerRole(userRole.Name) != model.RoleAdmin {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrAdminAccessRequired)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrAdminAccessRequired)))
}
new_token, err := h.storageService.NewWebdavToken(userID)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(&new_token, 0, i18n.T_(c, response.Message_OK)))
}

View 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)
}

View File

@@ -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/public"
"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"
"github.com/gofiber/fiber/v3"
@@ -25,6 +26,7 @@ import (
type Server struct {
app *fiber.App
cfg *config.Config
webdav fiber.Router
api fiber.Router
public fiber.Router
restricted fiber.Router
@@ -42,12 +44,23 @@ func (s *Server) Cfg() *config.Config {
// New creates a new server instance
func New() *Server {
return &Server{
app: fiber.New(fiber.Config{
ErrorHandler: customErrorHandler,
}),
cfg: config.Get(),
}
var s Server
app :=
fiber.New(fiber.Config{
ErrorHandler: customErrorHandler,
BodyLimit: 50 * 1024 * 1024, // 50 MB
StreamRequestBody: true,
RequestMethods: []string{
fiber.MethodGet, fiber.MethodHead, fiber.MethodPost, fiber.MethodPut,
fiber.MethodDelete, fiber.MethodConnect, fiber.MethodOptions,
fiber.MethodTrace, fiber.MethodPatch, "MKCOL", "PROPFIND", "PROPPATCH", "MOVE", "COPY",
},
})
s.app = app
s.cfg = config.Get()
return &s
}
// Setup configures the server with routes and middleware
@@ -76,6 +89,8 @@ func (s *Server) Setup() error {
s.public = s.api.Group("/public")
s.restricted = s.api.Group("/restricted")
s.restricted.Use(middleware.AuthMiddleware())
s.webdav = s.api.Group("/webdav")
s.webdav.Use(middleware.Webdav())
// initialize language endpoints (general)
api.NewLangHandler().InitLanguage(s.api, s.cfg)
@@ -90,13 +105,15 @@ func (s *Server) Setup() error {
menuRouting := s.public.Group("/menu")
public.RoutingHandlerRoutes(menuRouting)
pCustomer := s.restricted.Group("/customer")
restricted.CustomerHandlerRoutes(pCustomer)
// product translation routes (restricted)
productTranslation := s.restricted.Group("/product-translation")
restricted.ProductTranslationHandlerRoutes(productTranslation)
// lists of things routes (restricted)
list := s.restricted.Group("/list")
restricted.ListHandlerRoutes(list)
product := s.restricted.Group("/product")
restricted.ProductsHandlerRoutes(product)
// locale selector (restricted)
// this is basically for changing user's selected language and country
@@ -115,6 +132,18 @@ func (s *Server) Setup() error {
carts := s.restricted.Group("/carts")
restricted.CartsHandlerRoutes(carts)
// 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 {
return c.SendStatus(fiber.StatusNotFound)
})

79
app/model/address.go Normal file
View File

@@ -0,0 +1,79 @@
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"`
AddressInfo string `gorm:"column:address_info;not null" json:"address_info"`
CountryID uint `gorm:"column:b2b_country_id;not null" json:"country_id"`
}
func (Address) TableName() string {
return "b2b_addresses"
}
type AddressUnparsed struct {
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
CustomerID uint `gorm:"column:b2b_customer_id;not null;index" json:"customer_id"`
AddressInfo AddressField `gorm:"column:address_info;not null" json:"address_info"`
CountryID uint `gorm:"column:b2b_country_id;not null" json:"country_id"`
}
type AddressField 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"`
}

25
app/model/currency.go Normal file
View File

@@ -0,0 +1,25 @@
package model
import "time"
type Currency struct {
ID int `json:"id"`
PsIDCurrency uint `json:"ps_id_currency"`
IsDefault bool `json:"is_default"`
IsActive bool `json:"is_active"`
ConversionRate *float64 `json:"conversion_rate,omitempty"`
}
func (Currency) TableName() string {
return "b2b_currencies"
}
type CurrencyRate struct {
B2bIdCurrency uint `json:"b2b_id_currency"`
CreatedAt time.Time `json:"created_at"`
ConversionRate *float64 `json:"conversion_rate,omitempty"`
}
func (CurrencyRate) TableName() string {
return "b2b_currency_rates"
}

View File

@@ -3,6 +3,7 @@ package model
import (
"time"
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
"gorm.io/gorm"
)
@@ -13,7 +14,8 @@ type Customer struct {
Password string `gorm:"size:255" json:"-"` // Hashed password, not exposed in JSON
FirstName string `gorm:"size:100" json:"first_name"`
LastName string `gorm:"size:100" json:"last_name"`
Role CustomerRole `gorm:"type:varchar(20);default:'user'" json:"role"`
RoleID uint `gorm:"column:role_id;not null;default:1" json:"-"`
Role *Role `gorm:"foreignKey:RoleID" json:"role,omitempty"`
Provider AuthProvider `gorm:"type:varchar(20);default:'local'" json:"provider"`
ProviderID string `gorm:"size:255" json:"provider_id,omitempty"` // ID from OAuth provider
AvatarURL string `gorm:"size:500" json:"avatar_url,omitempty"`
@@ -23,6 +25,8 @@ type Customer struct {
EmailVerificationExpires *time.Time `json:"-"`
PasswordResetToken string `gorm:"size:255" json:"-"`
PasswordResetExpires *time.Time `json:"-"`
WebdavToken string `gorm:"size:255" json:"-"`
WebdavExpires *time.Time `json:"-"`
LastPasswordResetRequest *time.Time `json:"-"`
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
LangID uint `gorm:"default:2" json:"lang_id"` // User's preferred language
@@ -32,13 +36,14 @@ type Customer struct {
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// CustomerRole represents the role of a user
type CustomerRole string
const (
RoleUser CustomerRole = "user"
RoleAdmin CustomerRole = "admin"
)
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
type AuthProvider string
@@ -53,16 +58,6 @@ func (Customer) TableName() string {
return "b2b_customers"
}
// IsAdmin checks if the user has admin role
func (u *Customer) IsAdmin() bool {
return u.Role == RoleAdmin
}
// CanManageUsers checks if the user can manage other users
func (u *Customer) CanManageUsers() bool {
return u.Role == RoleAdmin
}
// FullName returns the user's full name
func (u *Customer) FullName() string {
if u.FirstName == "" && u.LastName == "" {
@@ -73,31 +68,65 @@ func (u *Customer) FullName() string {
// UserSession represents a user session for JWT claims
type UserSession struct {
UserID uint `json:"user_id"`
Email string `json:"email"`
Username string `json:"username"`
Role CustomerRole `json:"role"`
LangID uint `json:"lang_id"`
CountryID uint `json:"country_id"`
IsActive bool `json:"is_active"`
UserID uint `json:"user_id"`
Email string `json:"email"`
Username string `json:"username"`
RoleID uint `json:"role_id"`
RoleName string `json:"role_name"`
LangID uint `json:"lang_id"`
CountryID uint `json:"country_id"`
IsActive bool `json:"is_active"`
Permissions []perms.Permission `json:"permissions"`
}
func (us *UserSession) HasPermission(permission perms.Permission) bool {
for _, p := range us.Permissions {
if p == permission {
return true
}
}
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
func (u *Customer) ToSession() *UserSession {
return &UserSession{
UserID: u.ID,
Email: u.Email,
Role: u.Role,
LangID: u.LangID,
CountryID: u.CountryID,
IsActive: u.IsActive,
UserID: u.ID,
Email: u.Email,
RoleID: u.Role.ID,
RoleName: u.Role.Name,
Permissions: BuildPermissionSlice(u),
LangID: u.LangID,
CountryID: u.CountryID,
IsActive: u.IsActive,
}
}
func BuildPermissionSlice(user *Customer) []perms.Permission {
var perms []perms.Permission
for _, p := range user.Role.Permissions {
perms = append(perms, p.Name)
}
return perms
}
// LoginRequest represents the login form data
type LoginRequest struct {
Email string `json:"email" form:"email"`
Password string `json:"password" form:"password"`
LangID *uint `json:"lang_id" form:"lang_id"`
}
// RegisterRequest represents the initial registration form data
@@ -150,5 +179,4 @@ type UserInList struct {
Email string `gorm:"column:email" json:"email"`
FirstName string `gorm:"column:first_name" json:"first_name"`
LastName string `gorm:"column:last_name" json:"last_name"`
Role string `gorm:"column:role" json:"role"`
}

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

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

18
app/model/model.go Normal file
View File

@@ -0,0 +1,18 @@
package model
import (
"time"
"gorm.io/gorm"
)
type Model struct {
ID uint `gorm:"primarykey;autoIncrement" swaggerignore:"true" json:"id,omitempty" hidden:"true"`
CreatedAt time.Time `gorm:"not null;autoCreateTime" swaggerignore:"true" json:"-"`
UpdatedAt time.Time `gorm:"autoUpdateTime" swaggerignore:"true" json:"-"`
DeletedAt gorm.DeletedAt `gorm:"index" swaggerignore:"true" json:"-"`
}
// Makes all objects embedding db.Model implementators of ModelWithID interface
func (m Model) ModelWithID() {
}

12
app/model/permission.go Normal file
View File

@@ -0,0 +1,12 @@
package model
import "git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
type Permission struct {
ID uint
Name perms.Permission
}
func (Permission) TableName() string {
return "b2b_permissions"
}

View File

@@ -20,7 +20,8 @@ type ProductDescription struct {
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"`
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
ImageLink string `gorm:"column:image_link" json:"image_link"`
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
}
type ProductRow struct {

19
app/model/role.go Normal file
View File

@@ -0,0 +1,19 @@
package model
type Role struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:64" json:"name"`
Permissions []Permission `gorm:"many2many:b2b_role_permissions;" json:"permissions"`
}
func (Role) TableName() string {
return "b2b_roles"
}
type CustomerRole string
const (
RoleUser CustomerRole = "user"
RoleAdmin CustomerRole = "admin"
RoleSuperAdmin CustomerRole = "super_admin"
)

View 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,
AddressInfo: 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,
AddressInfo: 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
}

View File

@@ -0,0 +1,53 @@
package currencyRepo
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/utils/query/filters"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
)
type UICurrencyRepo interface {
CreateConversionRate(currencyRate *model.CurrencyRate) error
Get(id uint) (*model.Currency, error)
}
type CurrencyRepo struct{}
func New() UICurrencyRepo {
return &CurrencyRepo{}
}
func (repo *CurrencyRepo) CreateConversionRate(currencyRate *model.CurrencyRate) error {
return db.DB.Create(currencyRate).Error
}
func (repo *CurrencyRepo) Get(id uint) (*model.Currency, error) {
var currency model.Currency
err := db.DB.Table("b2b_currencies c").
Select("c.*, r.conversion_rate").
Joins(`
LEFT JOIN b2b_currency_rates r
ON r.b2b_id_currency = c.id
AND r.created_at = (
SELECT MAX(created_at)
FROM b2b_currency_rates
WHERE b2b_id_currency = c.id
)
`).
Where("c.id = ?", id).
Scan(&currency).Error
return &currency, err
}
func (repo *CurrencyRepo) Find(langId uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.Currency], error) {
found, err := find.Paginate[model.Currency](langId, p, db.DB.
Model(&model.Currency{}).
Scopes(filt.All()...),
)
return &found, err
}

View File

@@ -0,0 +1,197 @@
package customerRepo
import (
"strings"
"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/utils/query/filters"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
)
type UICustomerRepo interface {
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{}
func New() UICustomerRepo {
return &CustomerRepo{}
}
func (repo *CustomerRepo) Get(id uint) (*model.Customer, error) {
var customer model.Customer
err := db.DB.
Preload("Role.Permissions").
First(&customer, id).
Error
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, `
(LOWER(first_name) LIKE ? OR
LOWER(last_name) LIKE ? OR
LOWER(email) LIKE ?)
`)
for range 3 {
args = append(args, "%"+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
}
// func (repo *CustomerRepo) Search(
// customerId uint,
// partnerCode string,
// p find.Paging,
// filt *filters.FiltersList,
// search string,
// ) (found find.Found[model.UserInList], err error) {
// words := strings.Fields(search)
// if len(words) > 5 {
// words = words[:5]
// }
// query := ctx.DB().
// Model(&model.Customer{}).
// Select("customer.id AS id, customer.first_name as first_name, customer.last_name as last_name, customer.phone_number AS phone_number, customer.email AS email, count(distinct investment_plan_contract.id) as iiplan_purchases, count(distinct `order`.id) as single_purchases, entity.name as entity_name").
// Where("customer.id <> ?", customerId).
// Where("(customer.id IN (SELECT id FROM customer WHERE partner_code IN (WITH RECURSIVE partners AS (SELECT code AS dst FROM partner WHERE code = ? UNION SELECT code FROM partner JOIN partners ON partners.dst = partner.superior_code) SELECT dst FROM partners)) OR customer.recommender_code = ?)", partnerCode, partnerCode).
// Scopes(view.CustomerListQuery())
// var conditions []string
// var args []interface{}
// for _, word := range words {
// conditions = append(conditions, `
// (LOWER(first_name) LIKE ? OR
// LOWER(last_name) LIKE ? OR
// phone_number LIKE ? OR
// LOWER(email) LIKE ?)
// `)
// for i := 0; i < 4; i++ {
// args = append(args, "%"+strings.ToLower(word)+"%")
// }
// }
// finalQuery := strings.Join(conditions, " AND ")
// query = query.Where(finalQuery, args...).
// Scopes(filt.All()...)
// found, err = find.Paginate[V](ctx, p, query)
// return found, errs.Recorded(span, err)
// }
// 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
// }

View File

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

View File

@@ -1,6 +1,9 @@
package listRepo
package productsRepo
import (
"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/model"
@@ -11,18 +14,39 @@ import (
"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 UIProductsRepo interface {
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, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error)
}
type ListRepo struct{}
type ProductsRepo struct{}
func New() UIListRepo {
return &ListRepo{}
func New() UIProductsRepo {
return &ProductsRepo{}
}
func (repo *ListRepo) ListProducts(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error) {
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) {
var productStr string // ← Scan as string first
err := db.DB.Raw(`CALL get_full_product(?,?,?,?,?,?)`,
p_id_product, p_id_shop, p_id_lang, p_id_customer, b2b_id_country, p_quantity).
Scan(&productStr).
Error
if err != nil {
return nil, err
}
// Optional: validate it's valid JSON
if !json.Valid([]byte(productStr)) {
return nil, fmt.Errorf("invalid json returned from stored procedure")
}
raw := json.RawMessage(productStr)
return &raw, nil
}
func (repo *ProductsRepo) Find(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error) {
var list []model.ProductInList
var total int64
@@ -52,7 +76,8 @@ func (repo *ListRepo) ListProducts(id_lang uint, p find.Paging, filt *filters.Fi
Name: "variants",
Subquery: exclause.Subquery{DB: db.Get().Model(&dbmodel.PsProductAttributeShop{}).Select("id_product", "COUNT(*) AS variants_number").Group("id_product")},
},
}})
}}).
Order("ps.id_product DESC")
// Apply all filters
if filt != nil {
@@ -66,7 +91,6 @@ func (repo *ListRepo) ListProducts(id_lang uint, p find.Paging, filt *filters.Fi
}
err = query.
Order("ps.id_product DESC").
Limit(p.Limit()).
Offset(p.Offset()).
Find(&list).Error
@@ -79,43 +103,3 @@ func (repo *ListRepo) ListProducts(id_lang uint, p find.Paging, filt *filters.Fi
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
}

View 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
}

View File

@@ -8,7 +8,7 @@ import (
type UIRoutesRepo interface {
GetRoutes(langId uint) ([]model.Route, error)
GetTopMenu(id uint) ([]model.B2BTopMenu, error)
GetTopMenu(id uint, roleId uint) ([]model.B2BTopMenu, error)
}
type RoutesRepo struct{}
@@ -26,12 +26,16 @@ func (p *RoutesRepo) GetRoutes(langId uint) ([]model.Route, error) {
return routes, nil
}
func (p *RoutesRepo) GetTopMenu(id uint) ([]model.B2BTopMenu, error) {
func (p *RoutesRepo) GetTopMenu(langId uint, roleId uint) ([]model.B2BTopMenu, error) {
var menus []model.B2BTopMenu
err := db.Get().
Where("active = ?", 1).
Order("parent_id ASC, position ASC").
err := db.
Get().
Model(model.B2BTopMenu{}).
Joins("JOIN b2b_top_menu_roles tmr ON tmr.top_menu_id = b2b_top_menu.menu_id").
Where(model.B2BTopMenu{Active: 1}).
Where("tmr.role_id = ?", roleId).
Order("b2b_top_menu.parent_id ASC, b2b_top_menu.position ASC").
Find(&menus).Error
return menus, err

View File

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

View File

@@ -0,0 +1,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
}

View File

@@ -0,0 +1,152 @@
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.AddressField, 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) RetrieveAddressesInfo(user_id uint) (*[]model.AddressUnparsed, error) {
parsed_addresses, err := s.repo.RetrieveAddresses(user_id)
if err != nil {
return nil, err
}
var unparsed_addresses []model.AddressUnparsed
for i := 0; i < len(*parsed_addresses); i++ {
var next_address model.AddressUnparsed
next_address.ID = (*parsed_addresses)[i].ID
next_address.CustomerID = (*parsed_addresses)[i].CustomerID
next_address.CountryID = (*parsed_addresses)[i].CountryID
next_address.AddressInfo, err = s.validateAddressJson((*parsed_addresses)[i].AddressInfo, next_address.CountryID)
// log such errors
if err != nil {
fmt.Printf("err: %v\n", err)
}
unparsed_addresses = append(unparsed_addresses, next_address)
}
return &unparsed_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.AddressField, 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
}
}

View File

@@ -11,6 +11,8 @@ 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/repos/customerRepo"
roleRepo "git.ma-al.com/goc_daniel/b2b/app/repos/rolesRepo"
"git.ma-al.com/goc_daniel/b2b/app/service/emailService"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
@@ -23,29 +25,33 @@ import (
// JWTClaims represents the JWT claims
type JWTClaims struct {
UserID uint `json:"user_id"`
Email string `json:"email"`
Username string `json:"username"`
Role model.CustomerRole `json:"customer_role"`
CartsIDs []uint `json:"carts_ids"`
LangID uint `json:"lang_id"`
CountryID uint `json:"country_id"`
UserID uint `json:"user_id"`
Email string `json:"email"`
Username string `json:"username"`
Role string `json:"customer_role"`
CartsIDs []uint `json:"carts_ids"`
LangID uint `json:"lang_id"`
CountryID uint `json:"country_id"`
jwt.RegisteredClaims
}
// AuthService handles authentication operations
type AuthService struct {
db *gorm.DB
config *config.AuthConfig
email *emailService.EmailService
db *gorm.DB
config *config.AuthConfig
email *emailService.EmailService
customerRepo customerRepo.UICustomerRepo
roleRepo roleRepo.UIRolesRepo
}
// NewAuthService creates a new AuthService instance
func NewAuthService() *AuthService {
svc := &AuthService{
db: db.Get(),
config: &config.Get().Auth,
email: emailService.NewEmailService(),
db: db.Get(),
config: &config.Get().Auth,
email: emailService.NewEmailService(),
customerRepo: customerRepo.New(),
roleRepo: roleRepo.New(),
}
// Auto-migrate the refresh_tokens table
if svc.db != nil {
@@ -59,7 +65,7 @@ func (s *AuthService) Login(req *model.LoginRequest) (*model.AuthResponse, strin
var user model.Customer
// Find user by email
if err := s.db.Where("email = ?", req.Email).First(&user).Error; err != nil {
if err := s.db.Preload("Role.Permissions").Where("email = ?", req.Email).First(&user).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, "", responseErrors.ErrInvalidCredentials
}
@@ -83,6 +89,15 @@ func (s *AuthService) Login(req *model.LoginRequest) (*model.AuthResponse, strin
// Update last login time
now := time.Now()
user.LastLoginAt = &now
if req.LangID != nil {
_, err := s.GetLangISOCode(*req.LangID)
if err != nil {
return nil, "", responseErrors.ErrBadLangID
}
user.LangID = *req.LangID
}
s.db.Save(&user)
// Generate access token (JWT)
@@ -144,7 +159,6 @@ func (s *AuthService) Register(req *model.RegisterRequest) error {
Password: string(hashedPassword),
FirstName: req.FirstName,
LastName: req.LastName,
Role: model.RoleUser,
Provider: model.ProviderLocal,
IsActive: false,
EmailVerified: false,
@@ -422,7 +436,7 @@ func (s *AuthService) RevokeAllRefreshTokens(userID uint) {
// GetUserByID retrieves a user by ID
func (s *AuthService) GetUserByID(userID uint) (*model.Customer, error) {
var user model.Customer
if err := s.db.First(&user, userID).Error; err != nil {
if err := s.db.Preload("Role.Permissions").First(&user, userID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, responseErrors.ErrUserNotFound
}
@@ -443,6 +457,19 @@ func (s *AuthService) GetUserByEmail(email string) (*model.Customer, error) {
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.
func (s *AuthService) createRefreshToken(userID uint) (string, error) {
// Generate 32 random bytes → 64-char hex string
@@ -489,7 +516,7 @@ func (s *AuthService) generateAccessToken(user *model.Customer) (string, error)
UserID: user.ID,
Email: user.Email,
Username: user.Email,
Role: user.Role,
Role: user.Role.Name,
CartsIDs: []uint{},
LangID: user.LangID,
CountryID: user.CountryID,

View File

@@ -108,26 +108,32 @@ func (s *AuthService) HandleGoogleCallback(code string) (*model.AuthResponse, st
// findOrCreateGoogleUser finds an existing user by Google provider ID or email,
// or creates a new one.
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
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 {
// Update avatar in case it changed
user.AvatarURL = info.Picture
s.db.Save(&user)
return &user, nil
err = s.customerRepo.Save(user)
if err != nil {
return nil, err
}
return user, nil
}
// 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 {
// Link Google provider to existing account
user.Provider = model.ProviderGoogle
user.ProviderID = info.ID
user.AvatarURL = info.Picture
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 !user.EmailVerified {
@@ -139,7 +145,7 @@ func (s *AuthService) findOrCreateGoogleUser(info *view.GoogleUserInfo) (*model.
}
user.EmailVerified = true
return &user, nil
return user, nil
}
// Create new user
@@ -148,16 +154,16 @@ func (s *AuthService) findOrCreateGoogleUser(info *view.GoogleUserInfo) (*model.
FirstName: info.GivenName,
LastName: info.FamilyName,
Provider: model.ProviderGoogle,
RoleID: 1, // user
ProviderID: info.ID,
AvatarURL: info.Picture,
Role: model.RoleUser,
IsActive: true,
EmailVerified: true,
LangID: 2, // default is english
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)
}
@@ -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
}

View File

@@ -0,0 +1,25 @@
package currencyService
import (
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/repos/currencyRepo"
)
type CurrencyService struct {
repo currencyRepo.UICurrencyRepo
}
func (s *CurrencyService) GetCurrency(id uint) (*model.Currency, error) {
return s.repo.Get(id)
}
func (s *CurrencyService) CreateCurrencyRate(currency *model.CurrencyRate) error {
return s.repo.CreateConversionRate(currency)
}
func New() *CurrencyService {
repo := currencyRepo.New()
return &CurrencyService{
repo: repo,
}
}

View File

@@ -0,0 +1,26 @@
package customerService
import (
"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/utils/query/filters"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
)
type CustomerService struct {
repo customerRepo.UICustomerRepo
}
func New() *CustomerService {
return &CustomerService{
repo: customerRepo.New(),
}
}
func (s *CustomerService) GetById(id uint) (*model.Customer, error) {
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)
}

View File

@@ -10,6 +10,7 @@ import (
"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/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/view"
)
@@ -133,6 +134,6 @@ func (s *EmailService) passwordResetEmailTemplate(name, resetURL string, langID
// newUserAdminNotificationTemplate returns the HTML template for admin notification
func (s *EmailService) newUserAdminNotificationTemplate(userEmail, userName, baseURL string) string {
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()
}

View File

@@ -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)
}

View File

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

View File

@@ -176,8 +176,8 @@ func (s *MenuService) GetBreadcrumb(root_category_id uint, start_category_id uin
return breadcrumb, nil
}
func (s *MenuService) GetTopMenu(id uint) ([]*model.B2BTopMenu, error) {
items, err := s.routesRepo.GetTopMenu(id)
func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BTopMenu, error) {
items, err := s.routesRepo.GetTopMenu(languageId, roleId)
if err != nil {
return nil, err
}

View File

@@ -0,0 +1,34 @@
package productService
import (
"encoding/json"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/repos/productsRepo"
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 ProductService struct {
productsRepo productsRepo.UIProductsRepo
}
func New() *ProductService {
return &ProductService{
productsRepo: productsRepo.New(),
}
}
func (s *ProductService) GetJSON(p_id_product, p_id_lang, p_id_customer, b2b_id_country, p_quantity int) (*json.RawMessage, error) {
products, err := s.productsRepo.GetJSON(p_id_product, constdata.SHOP_ID, p_id_lang, p_id_customer, b2b_id_country, p_quantity)
if err != nil {
return products, err
}
return products, nil
}
func (s *ProductService) Find(id_lang uint, p find.Paging, filters *filters.FiltersList) (find.Found[model.ProductInList], error) {
return s.productsRepo.Find(id_lang, p, filters)
}

View File

@@ -89,13 +89,24 @@ func (s *ProductTranslationService) GetProductDescription(userID uint, productID
// Updates relevant fields with the "updates" map
func (s *ProductTranslationService) SaveProductDescription(userID uint, productID uint, productLangID uint, updates map[string]string) error {
// 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 {
if !slices.Contains(allowedFields, key) {
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
mustBeHTML := []string{"description", "description_short", "usage"}
for i := 0; i < len(mustBeHTML); i++ {
@@ -136,20 +147,28 @@ func (s *ProductTranslationService) TranslateProductDescription(userID uint, pro
fields := []*string{&productDescription.Description,
&productDescription.DescriptionShort,
&productDescription.LinkRewrite,
&productDescription.MetaDescription,
&productDescription.MetaKeywords,
&productDescription.MetaTitle,
&productDescription.Name,
&productDescription.AvailableNow,
&productDescription.AvailableLater,
&productDescription.DeliveryInStock,
&productDescription.DeliveryOutStock,
&productDescription.Usage,
}
keys := []string{"translation_of_product_description",
"translation_of_product_short_description",
"translation_of_product_url_link",
"translation_of_product_meta_description",
"translation_of_product_meta_keywords",
"translation_of_product_meta_title",
"translation_of_product_name",
"translation_of_product_available_now",
"translation_of_product_available_later",
"translation_of_product_available_now_message",
"translation_of_product_available_later_message",
"translation_of_product_delivery_in_stock_message",
"translation_of_product_delivery_out_stock_message",
"translation_of_product_usage",
}

View File

@@ -0,0 +1,69 @@
package productTranslationService
import (
"strings"
"unicode"
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 = transliterateWithTable(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 transliterateWithTable(s string) string {
var b strings.Builder
b.Grow(len(s))
for _, r := range s {
if repl, ok := constdata.TRANSLITERATION_TABLE[r]; ok {
b.WriteString(repl)
} else {
b.WriteRune(r)
}
}
return b.String()
}
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
}

View 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
}
}

View File

@@ -4,6 +4,7 @@ package constdata
const PASSWORD_VALIDATION_REGEX = `^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$`
const SHOP_ID = 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
const CATEGORY_TREE_ROOT_ID = 2
@@ -11,5 +12,36 @@ const CATEGORY_TREE_ROOT_ID = 2
const MAX_AMOUNT_OF_CARTS_PER_USER = 10
const DEFAULT_NEW_CART_NAME = "new cart"
const USER_LOCALES_NAME = "user"
const USER_LOCALES_ID = "userID"
const MAX_AMOUNT_OF_ADDRESSES_PER_USER = 10
const USER_LOCALE = "user"
// 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]+)*$`
// Currently supports only German+Polish specific cases
var TRANSLITERATION_TABLE = map[rune]string{
// German
'ä': "ae",
'ö': "oe",
'ü': "ue",
'ß': "ss",
// Polish
'ą': "a",
'ć': "c",
'ę': "e",
'ł': "l",
'ń': "n",
'ó': "o",
'ś': "s",
'ż': "z",
'ź': "z",
}

View File

@@ -8,6 +8,7 @@ import (
"sync"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"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.
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), ".")
if len(parts) >= 2 {

View File

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

View File

@@ -1,7 +1,6 @@
package find
import (
"errors"
"reflect"
"strings"
@@ -28,18 +27,13 @@ type Found[T any] struct {
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) {
var items []T
var count uint64
var count int64
// stmt.Debug()
stmt.Count(&count)
err := stmt.
Clauses(SqlCalcFound()).
Offset(paging.Offset()).
Limit(paging.Limit()).
Find(&items).
@@ -48,22 +42,14 @@ func Paginate[T any](langID uint, paging Paging, stmt *gorm.DB) (Found[T], error
return Found[T]{}, err
}
countInterface, ok := stmt.Get(FOUND_ROWS_CTX_KEY)
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)
// columnsSpec := GetColumnsSpec[T](langID)
return Found[T]{
Items: items,
Count: uint(count),
Spec: map[string]interface{}{
"columns": columnsSpec,
},
// Spec: map[string]interface{}{
// "columns": columnsSpec,
// },
}, err
}

View File

@@ -9,13 +9,15 @@ import (
var (
// Typed errors for request validation and authentication
ErrInvalidBody = errors.New("invalid request body")
ErrNotAuthenticated = errors.New("not authenticated")
ErrUserNotFound = errors.New("user not found")
ErrUserInactive = errors.New("user account is inactive")
ErrInvalidToken = errors.New("invalid token")
ErrTokenExpired = errors.New("token has expired")
ErrTokenRequired = errors.New("token is required")
ErrForbidden = errors.New("forbidden")
ErrInvalidBody = errors.New("invalid request body")
ErrNotAuthenticated = errors.New("not authenticated")
ErrUserNotFound = errors.New("user not found")
ErrUserInactive = errors.New("user account is inactive")
ErrInvalidToken = errors.New("invalid token")
ErrTokenExpired = errors.New("token has expired")
ErrTokenRequired = errors.New("token is required")
ErrAdminAccessRequired = errors.New("admin access required")
// Typed errors for logging in and registering
ErrInvalidCredentials = errors.New("invalid email or password")
@@ -42,6 +44,7 @@ var (
// Typed errors for product description handler
ErrBadAttribute = errors.New("bad or missing attribute value in header")
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")
ErrAIResponseFail = errors.New("AI responded with failure")
ErrAIBadOutput = errors.New("AI response does not obey the format")
@@ -59,6 +62,22 @@ var (
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
ErrUserHasNoSuchCart = errors.New("user does not have cart with given id")
ErrProductOrItsVariationDoesNotExist = errors.New("product or its variation with given ids does not exist")
// Typed errors for storage
ErrAccessDenied = errors.New("access denied!")
ErrFolderDoesNotExist = errors.New("folder does not exist")
ErrFileDoesNotExist = errors.New("file does not exist")
ErrNameTaken = errors.New("name taken")
ErrMissingFileFieldDocument = errors.New("missing file field 'document'")
// Typed errors for data parsing
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
@@ -83,6 +102,8 @@ func NewError(err error, status int) *Error {
// GetErrorCode returns the error code string for HTTP response mapping
func GetErrorCode(c fiber.Ctx, err error) string {
switch {
case errors.Is(err, ErrForbidden):
return i18n.T_(c, "error.err_forbidden")
case errors.Is(err, ErrInvalidBody):
return i18n.T_(c, "error.err_invalid_body")
case errors.Is(err, ErrInvalidCredentials):
@@ -111,6 +132,8 @@ func GetErrorCode(c fiber.Ctx, err error) string {
return i18n.T_(c, "error.err_token_required")
case errors.Is(err, ErrRefreshTokenRequired):
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):
return i18n.T_(c, "error.err_bad_lang_id")
case errors.Is(err, ErrBadCountryID):
@@ -136,6 +159,8 @@ func GetErrorCode(c fiber.Ctx, err error) string {
return i18n.T_(c, "error.err_bad_attribute")
case errors.Is(err, ErrBadField):
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):
return i18n.T_(c, "error.err_invalid_html")
case errors.Is(err, ErrAIResponseFail):
@@ -147,20 +172,43 @@ func GetErrorCode(c fiber.Ctx, err error) string {
return i18n.T_(c, "error.err_bad_paging")
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):
return i18n.T_(c, "error.circular_dependency")
return i18n.T_(c, "error.err_circular_dependency")
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):
return i18n.T_(c, "error.root_never_reached")
return i18n.T_(c, "error.err_root_never_reached")
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):
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):
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, 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, ErrJSONBody):
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:
return i18n.T_(c, "error.err_internal_server_error")
@@ -170,6 +218,8 @@ func GetErrorCode(c fiber.Ctx, err error) string {
// GetErrorStatus returns the HTTP status code for the given error
func GetErrorStatus(err error) int {
switch {
case errors.Is(err, ErrForbidden):
return fiber.StatusForbidden
case errors.Is(err, ErrInvalidCredentials),
errors.Is(err, ErrNotAuthenticated),
errors.Is(err, ErrInvalidToken),
@@ -184,6 +234,7 @@ func GetErrorStatus(err error) int {
errors.Is(err, ErrEmailPasswordRequired),
errors.Is(err, ErrTokenRequired),
errors.Is(err, ErrRefreshTokenRequired),
errors.Is(err, ErrAdminAccessRequired),
errors.Is(err, ErrBadLangID),
errors.Is(err, ErrBadCountryID),
errors.Is(err, ErrPasswordsDoNotMatch),
@@ -195,6 +246,7 @@ func GetErrorStatus(err error) int {
errors.Is(err, ErrInvalidPassword),
errors.Is(err, ErrBadAttribute),
errors.Is(err, ErrBadField),
errors.Is(err, ErrInvalidURLSlug),
errors.Is(err, ErrInvalidXHTML),
errors.Is(err, ErrBadPaging),
errors.Is(err, ErrNoRootFound),
@@ -203,7 +255,17 @@ func GetErrorStatus(err error) int {
errors.Is(err, ErrRootNeverReached),
errors.Is(err, ErrMaxAmtOfCartsReached),
errors.Is(err, ErrUserHasNoSuchCart),
errors.Is(err, ErrProductOrItsVariationDoesNotExist):
errors.Is(err, ErrProductOrItsVariationDoesNotExist),
errors.Is(err, ErrAccessDenied),
errors.Is(err, ErrFolderDoesNotExist),
errors.Is(err, ErrFileDoesNotExist),
errors.Is(err, ErrNameTaken),
errors.Is(err, ErrMissingFileFieldDocument),
errors.Is(err, ErrJSONBody),
errors.Is(err, ErrMaxAmtOfAddressesReached),
errors.Is(err, ErrUserHasNoSuchAddress),
errors.Is(err, ErrInvalidCountryID),
errors.Is(err, ErrInvalidAddressJSON):
return fiber.StatusBadRequest
case errors.Is(err, ErrEmailExists):
return fiber.StatusConflict

1
bo/components.d.ts vendored
View File

@@ -13,7 +13,6 @@ declare module 'vue' {
export interface GlobalComponents {
CartDetails: typeof import('./src/components/customer/CartDetails.vue')['default']
CategoryMenu: typeof import('./src/components/inner/categoryMenu.vue')['default']
CategoryMenuListing: typeof import('./src/components/inner/categoryMenuListing.vue')['default']
Cs_PrivacyPolicyView: typeof import('./src/components/terms/cs_PrivacyPolicyView.vue')['default']
Cs_TermsAndConditionsView: typeof import('./src/components/terms/cs_TermsAndConditionsView.vue')['default']
En_PrivacyPolicyView: typeof import('./src/components/terms/en_PrivacyPolicyView.vue')['default']

View File

@@ -1,7 +1,7 @@
info:
name: Change Locales
type: http
seq: 4
seq: 3
http:
method: POST

View File

@@ -1,7 +1,7 @@
info:
name: Create Search Index
type: http
seq: 2
seq: 1
http:
method: GET

View File

@@ -1,7 +1,7 @@
info:
name: Delete Index - MeiliSearch
type: http
seq: 7
seq: 5
http:
method: DELETE

View File

@@ -1,7 +1,7 @@
info:
name: Search Index Settings
type: http
seq: 5
seq: 4
http:
method: POST

View File

@@ -1,7 +1,7 @@
info:
name: Search Items
type: http
seq: 3
seq: 2
http:
method: POST

View File

@@ -0,0 +1,29 @@
info:
name: Login
type: http
seq: 1
http:
method: POST
url: "{{bas_url}}/public/auth/login"
body:
type: json
data: |-
{
"email":"{{email}}",
"password":"{{password}}"
}
auth: inherit
runtime:
variables:
- name: email
value: admin@ma-al.com
- name: password
value: Maal12345678
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

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

View File

@@ -0,0 +1,22 @@
info:
name: currency-rate
type: http
seq: 2
http:
method: POST
url: "{{bas_url}}/restricted/currency-rate"
body:
type: json
data: |-
{
"b2b_id_currency" : 1,
"conversion_rate": 4.2
}
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,20 @@
info:
name: currency
type: http
seq: 1
http:
method: GET
url: "{{bas_url}}/restricted/currency-rate/{{id}}"
auth: inherit
runtime:
variables:
- name: id
value: "1"
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

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

View File

@@ -0,0 +1,15 @@
info:
name: Customer (me)
type: http
seq: 2
http:
method: GET
url: "{{bas_url}}/restricted/customer"
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,19 @@
info:
name: Customer (other)
type: http
seq: 9
http:
method: GET
url: "{{bas_url}}/restricted/customer?id=1"
params:
- name: id
value: "1"
type: query
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,19 @@
info:
name: Customer list
type: http
seq: 3
http:
method: GET
url: "{{bas_url}}/restricted/customer/list?search="
params:
- name: search
value: ""
type: query
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

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

View File

@@ -0,0 +1,15 @@
info:
name: Get Product
type: http
seq: 1
http:
method: GET
url: "{{bas_url}}/restricted/product/200/1/5"
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -5,7 +5,7 @@ info:
http:
method: GET
url: "{{bas_url}}/restricted/list/list-products?p=1&elems=30&sort=product_id,asc&category_id_in=243&reference=~62"
url: "{{bas_url}}/restricted/product/list?p=1&elems=30&sort=product_id,asc&category_id_in=243&reference=~62"
params:
- name: p
value: "1"
@@ -25,9 +25,6 @@ http:
body:
type: json
data: ""
auth:
type: bearer
token: "{{token}}"
settings:
encodeUrl: true

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -0,0 +1,31 @@
info:
name: add-new-address
type: http
seq: 1
http:
method: POST
url: http://localhost:3000/api/v1/restricted/addresses/add-new-address?country_id=1
params:
- name: country_id
value: "1"
type: query
body:
type: json
data: |-
{
"postal_code": "31-154",
"city": "Kraków",
"voivodeship": "małopolskie",
"street": "Długa",
"building_no": "5",
"apartment_no": "7",
"recipient": "Jan Kowalski"
}
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -0,0 +1,19 @@
info:
name: delete-address
type: http
seq: 4
http:
method: DELETE
url: http://localhost:3000/api/v1/restricted/addresses/delete-address?address_id=1
params:
- name: address_id
value: "1"
type: query
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

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

View File

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

View File

@@ -0,0 +1,33 @@
info:
name: modify-address
type: http
seq: 2
http:
method: POST
url: http://localhost:3000/api/v1/restricted/addresses/modify-address?country_id=1&address_id=1
params:
- name: country_id
value: "1"
type: query
- name: address_id
value: "1"
type: query
body:
type: json
data: |-
{
"postal_code": "31-154",
"city": "Kraków",
"voivodeship": "śląskie",
"street": "Długa",
"building_no": "5",
"recipient": "Adam Adamowicz"
}
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More