Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into storage

This commit is contained in:
Daniel Goc
2026-04-08 13:19:45 +02:00
57 changed files with 1839 additions and 276 deletions

View File

@@ -18,3 +18,28 @@ const USER_LOCALE = "user"
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

@@ -22,10 +22,18 @@ func GetUserID(c fiber.Ctx) (uint, bool) {
return user_locale.User.ID, true
}
func GetOriginalUserRole(c fiber.Ctx) (model.CustomerRole, bool) {
func GetOriginalUserRole(c fiber.Ctx) (model.Role, bool) {
user_locale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
if !ok || user_locale.OriginalUser == nil {
return "", false
if !ok || user_locale.OriginalUser == nil || user_locale.OriginalUser.Role == nil {
return model.Role{}, false
}
return user_locale.OriginalUser.Role, true
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,6 +9,7 @@ import (
var (
// Typed errors for request validation and authentication
ErrForbidden = errors.New("forbidden")
ErrInvalidBody = errors.New("invalid request body")
ErrNotAuthenticated = errors.New("not authenticated")
ErrUserNotFound = errors.New("user not found")
@@ -16,7 +17,7 @@ var (
ErrInvalidToken = errors.New("invalid token")
ErrTokenExpired = errors.New("token has expired")
ErrTokenRequired = errors.New("token is required")
ErrAdminAccessRequired = errors.New("admin access is required")
ErrAdminAccessRequired = errors.New("admin access required")
// Typed errors for logging in and registering
ErrInvalidCredentials = errors.New("invalid email or password")
@@ -43,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")
@@ -67,6 +69,9 @@ var (
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")
)
// Error represents an error with HTTP status code
@@ -91,6 +96,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):
@@ -146,6 +153,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.invalid_url_slug")
case errors.Is(err, ErrInvalidXHTML):
return i18n.T_(c, "error.err_invalid_html")
case errors.Is(err, ErrAIResponseFail):
@@ -183,6 +192,9 @@ func GetErrorCode(c fiber.Ctx, err error) string {
case errors.Is(err, ErrMissingFileFieldDocument):
return i18n.T_(c, "error.missing_file_field_document")
case errors.Is(err, ErrJSONBody):
return i18n.T_(c, "error.err_json_body")
default:
return i18n.T_(c, "error.err_internal_server_error")
}
@@ -191,6 +203,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),
@@ -217,6 +231,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),
@@ -230,7 +245,8 @@ func GetErrorStatus(err error) int {
errors.Is(err, ErrFolderDoesNotExist),
errors.Is(err, ErrFileDoesNotExist),
errors.Is(err, ErrNameTaken),
errors.Is(err, ErrMissingFileFieldDocument):
errors.Is(err, ErrMissingFileFieldDocument),
errors.Is(err, ErrJSONBody):
return fiber.StatusBadRequest
case errors.Is(err, ErrEmailExists):
return fiber.StatusConflict