Compare commits
7 Commits
451e42a1fc
...
79ffb5051c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79ffb5051c | ||
|
|
5de2f31e5f | ||
|
|
b2e4947d37 | ||
|
|
6aee416b63 | ||
|
|
02d75f0ab1 | ||
|
|
3b3dc9d251 | ||
|
|
c008bcad0b |
@@ -68,38 +68,33 @@ export function isEmpty(obj) {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Test whether or not an object is empty.
|
|
||||||
* @param {any} obj - The object to test
|
|
||||||
* @returns `true` or `false`
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function formatError(obj) {
|
export function formatError(obj) {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
if (typeof obj === "object" && obj !== null) {
|
if (typeof obj === "object" && obj !== null) {
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
obj.forEach((/** @type {Object} */ error) => {
|
obj.forEach((error) => {
|
||||||
Object.keys(error).map((k) => {
|
|
||||||
errors.push({
|
errors.push({
|
||||||
error: error[k],
|
field: error.field,
|
||||||
|
key: error.key,
|
||||||
id: Math.random() * 1000,
|
id: Math.random() * 1000,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
Object.keys(obj).map((k) => {
|
Object.keys(obj).forEach((field) => {
|
||||||
errors.push({
|
errors.push({
|
||||||
error: obj[k],
|
field: field,
|
||||||
|
key: obj[field].key,
|
||||||
id: Math.random() * 1000,
|
id: Math.random() * 1000,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.push({
|
errors.push({
|
||||||
error: obj.charAt(0).toUpperCase() + obj.slice(1),
|
field: "general",
|
||||||
|
key: obj,
|
||||||
id: 0,
|
id: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ export const actions = {
|
|||||||
console.dir(formData);
|
console.dir(formData);
|
||||||
console.dir(cleanUpdateData);
|
console.dir(cleanUpdateData);
|
||||||
const apiURL = `${BASE_API_URI}/backend/users/update/`;
|
const apiURL = `${BASE_API_URI}/backend/users/update/`;
|
||||||
const res = await fetch(apiURL, {
|
|
||||||
|
/** @type {RequestInit} */
|
||||||
|
const requestUpdateOptions = {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -98,11 +100,12 @@ export const actions = {
|
|||||||
Cookie: `jwt=${cookies.get("jwt")}`,
|
Cookie: `jwt=${cookies.get("jwt")}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(cleanUpdateData),
|
body: JSON.stringify(cleanUpdateData),
|
||||||
});
|
};
|
||||||
|
const res = await fetch(apiURL, requestUpdateOptions);
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const response = await res.json();
|
const response = await res.json();
|
||||||
const errors = formatError(response.error);
|
const errors = formatError(response.errors);
|
||||||
return fail(400, { errors: errors });
|
return fail(400, { errors: errors });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +162,7 @@ export const actions = {
|
|||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const response = await res.json();
|
const response = await res.json();
|
||||||
const errors = formatError(response.error);
|
const errors = formatError(response.errors);
|
||||||
return fail(400, { errors: errors });
|
return fail(400, { errors: errors });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +198,7 @@ export const actions = {
|
|||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const response = await res.json();
|
const response = await res.json();
|
||||||
const errors = formatError(response.error);
|
const errors = formatError(response.errors);
|
||||||
return fail(400, { errors: errors });
|
return fail(400, { errors: errors });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,12 @@
|
|||||||
password2 = "";
|
password2 = "";
|
||||||
|
|
||||||
const open = () => (showModal = true);
|
const open = () => (showModal = true);
|
||||||
const close = () => (showModal = false);
|
const close = () => {
|
||||||
|
showModal = false;
|
||||||
|
if (form) {
|
||||||
|
form.errors = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
// const toggleAvatars = () => (showAvatars = !showAvatars);
|
// const toggleAvatars = () => (showAvatars = !showAvatars);
|
||||||
|
|
||||||
$: selectedSubscriptionModel =
|
$: selectedSubscriptionModel =
|
||||||
@@ -222,7 +227,7 @@
|
|||||||
in:receive={{ key: error.id }}
|
in:receive={{ key: error.id }}
|
||||||
out:send={{ key: error.id }}
|
out:send={{ key: error.id }}
|
||||||
>
|
>
|
||||||
{error.error}
|
{$t(error.field) + ": " + $t(error.key)}
|
||||||
</h4>
|
</h4>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -44,18 +44,9 @@ export const actions = {
|
|||||||
console.log("Login response headers:", Object.fromEntries(res.headers));
|
console.log("Login response headers:", Object.fromEntries(res.headers));
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
let errorMessage = `HTTP error! status: ${res.status}`;
|
|
||||||
try {
|
|
||||||
const errorData = await res.json();
|
const errorData = await res.json();
|
||||||
errorMessage = errorData.error || errorMessage;
|
const errors = formatError(errorData.errors);
|
||||||
} catch (parseError) {
|
return fail(res.status, { errors });
|
||||||
console.error("Failed to parse error response:", parseError);
|
|
||||||
errorMessage = await res.text(); // Get the raw response text if JSON parsing fails
|
|
||||||
}
|
|
||||||
console.error("Login failed:", errorMessage);
|
|
||||||
return fail(res.status, {
|
|
||||||
errors: [{ error: errorMessage, id: Date.now() }],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseBody = await res.json();
|
const responseBody = await res.json();
|
||||||
@@ -81,12 +72,4 @@ export const actions = {
|
|||||||
console.log("Redirecting to:", next || "/");
|
console.log("Redirecting to:", next || "/");
|
||||||
throw redirect(303, next || "/");
|
throw redirect(303, next || "/");
|
||||||
},
|
},
|
||||||
// if (!res.ok) {
|
|
||||||
// const response = await res.json();
|
|
||||||
// const errors = formatError(response.error);
|
|
||||||
// return fail(400, { errors: errors });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// throw redirect(303, next || "/");
|
|
||||||
// },
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
in:receive={{ key: error.id }}
|
in:receive={{ key: error.id }}
|
||||||
out:send={{ key: error.id }}
|
out:send={{ key: error.id }}
|
||||||
>
|
>
|
||||||
{error.error}
|
{$t(error.key)}
|
||||||
</h4>
|
</h4>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -26,6 +26,6 @@ func testXSSAttempt(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusNotAcceptable, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.NotContains(t, w.Body.String(), xssPayload)
|
assert.NotContains(t, w.Body.String(), xssPayload)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"GoMembership/internal/repositories"
|
"GoMembership/internal/repositories"
|
||||||
"GoMembership/internal/services"
|
"GoMembership/internal/services"
|
||||||
"GoMembership/internal/utils"
|
"GoMembership/internal/utils"
|
||||||
|
"GoMembership/internal/validation"
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -102,7 +103,9 @@ func TestSuite(t *testing.T) {
|
|||||||
var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
|
var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
|
||||||
userService := &services.UserService{Repo: userRepo, Licences: licenceRepo}
|
userService := &services.UserService{Repo: userRepo, Licences: licenceRepo}
|
||||||
|
|
||||||
Uc = &UserController{Service: userService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
|
driversLicenceService := &services.DriversLicenceService{Repo: licenceRepo}
|
||||||
|
|
||||||
|
Uc = &UserController{Service: userService, DriversLicenceService: driversLicenceService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
|
||||||
Mc = &MembershipController{Service: *membershipService}
|
Mc = &MembershipController{Service: *membershipService}
|
||||||
Cc = &ContactController{EmailService: emailService}
|
Cc = &ContactController{EmailService: emailService}
|
||||||
|
|
||||||
@@ -110,6 +113,10 @@ func TestSuite(t *testing.T) {
|
|||||||
log.Fatalf("Failed to init Subscription plans: %#v", err)
|
log.Fatalf("Failed to init Subscription plans: %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := initLicenceCategories(); err != nil {
|
||||||
|
log.Fatalf("Failed to init Categories: %v", err)
|
||||||
|
}
|
||||||
|
validation.SetupValidators()
|
||||||
t.Run("userController", func(t *testing.T) {
|
t.Run("userController", func(t *testing.T) {
|
||||||
testUserController(t)
|
testUserController(t)
|
||||||
})
|
})
|
||||||
@@ -139,6 +146,35 @@ func TestSuite(t *testing.T) {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initLicenceCategories() error {
|
||||||
|
categories := []models.LicenceCategory{
|
||||||
|
{Category: "AM"},
|
||||||
|
{Category: "A1"},
|
||||||
|
{Category: "A2"},
|
||||||
|
{Category: "A"},
|
||||||
|
{Category: "B"},
|
||||||
|
{Category: "C1"},
|
||||||
|
{Category: "C"},
|
||||||
|
{Category: "D1"},
|
||||||
|
{Category: "D"},
|
||||||
|
{Category: "BE"},
|
||||||
|
{Category: "C1E"},
|
||||||
|
{Category: "CE"},
|
||||||
|
{Category: "D1E"},
|
||||||
|
{Category: "DE"},
|
||||||
|
{Category: "T"},
|
||||||
|
{Category: "L"},
|
||||||
|
}
|
||||||
|
for _, category := range categories {
|
||||||
|
result := database.DB.Create(&category)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func initSubscriptionPlans() error {
|
func initSubscriptionPlans() error {
|
||||||
subscriptions := []models.SubscriptionModel{
|
subscriptions := []models.SubscriptionModel{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ func (mc *MembershipController) RegisterSubscription(c *gin.Context) {
|
|||||||
if err := c.ShouldBindJSON(®Data); err != nil {
|
if err := c.ShouldBindJSON(®Data); err != nil {
|
||||||
logger.Error.Printf("Couln't decode subscription data: %v", err)
|
logger.Error.Printf("Couln't decode subscription data: %v", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Couldn't decode subscription data"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Couldn't decode subscription data"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register Subscription
|
// Register Subscription
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ func getSubscriptionData() []RegisterSubscriptionTest {
|
|||||||
return []RegisterSubscriptionTest{
|
return []RegisterSubscriptionTest{
|
||||||
{
|
{
|
||||||
Name: "Missing details should fail",
|
Name: "Missing details should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": "Just a Subscription"},
|
WantDBData: map[string]interface{}{"name": "Just a Subscription"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
@@ -97,7 +97,7 @@ func getSubscriptionData() []RegisterSubscriptionTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Missing model name should fail",
|
Name: "Missing model name should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": ""},
|
WantDBData: map[string]interface{}{"name": ""},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(
|
Input: GenerateInputJSON(
|
||||||
@@ -108,7 +108,7 @@ func getSubscriptionData() []RegisterSubscriptionTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Negative monthly fee should fail",
|
Name: "Negative monthly fee should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeSubscription(func(sub MembershipData) MembershipData {
|
Input: GenerateInputJSON(customizeSubscription(func(sub MembershipData) MembershipData {
|
||||||
@@ -118,7 +118,7 @@ func getSubscriptionData() []RegisterSubscriptionTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Negative hourly rate should fail",
|
Name: "Negative hourly rate should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"name": "Premium"},
|
WantDBData: map[string]interface{}{"name": "Premium"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeSubscription(func(sub MembershipData) MembershipData {
|
Input: GenerateInputJSON(customizeSubscription(func(sub MembershipData) MembershipData {
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import (
|
|||||||
"GoMembership/internal/models"
|
"GoMembership/internal/models"
|
||||||
"GoMembership/internal/services"
|
"GoMembership/internal/services"
|
||||||
"GoMembership/internal/utils"
|
"GoMembership/internal/utils"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
|
||||||
"GoMembership/pkg/errors"
|
"GoMembership/pkg/errors"
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
@@ -33,13 +35,34 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
|
|||||||
var user models.User
|
var user models.User
|
||||||
if err := c.ShouldBindJSON(&user); err != nil {
|
if err := c.ShouldBindJSON(&user); err != nil {
|
||||||
logger.Error.Printf("Couldn't decode input: %v", err)
|
logger.Error.Printf("Couldn't decode input: %v", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Couldn't decode request data"})
|
var validationErrors []gin.H
|
||||||
|
if ve, ok := err.(validator.ValidationErrors); ok {
|
||||||
|
for _, e := range ve {
|
||||||
|
|
||||||
|
validationErrors = append(validationErrors, gin.H{
|
||||||
|
"field": e.Field(),
|
||||||
|
"key": "server.validation." + e.Tag(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validationErrors = append(validationErrors, gin.H{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.invalid_json",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
logger.Error.Printf("ValidationErrors: %#v", validationErrors)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"errors": validationErrors})
|
||||||
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logger.Error.Print("Continuing...")
|
||||||
tokenString, err := c.Cookie("jwt")
|
tokenString, err := c.Cookie("jwt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("No Auth token: %v\n", err)
|
logger.Error.Printf("No Auth token: %v\n", err)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "No Auth token"})
|
c.JSON(http.StatusUnauthorized, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.no_auth_token",
|
||||||
|
}}})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -47,7 +70,10 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
logger.Error.Printf("Error retrieving token and claims from JWT")
|
logger.Error.Printf("Error retrieving token and claims from JWT")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "JWT parsing error"})
|
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.jwt_parsing_error",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jwtUserID := uint((*claims)["user_id"].(float64))
|
jwtUserID := uint((*claims)["user_id"].(float64))
|
||||||
@@ -55,22 +81,27 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if user.ID == 0 {
|
if user.ID == 0 {
|
||||||
logger.Error.Printf("No User.ID in request from user with id: %v, aborting", jwtUserID)
|
logger.Error.Printf("No User.ID in request from user with id: %v, aborting", jwtUserID)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "No user id provided"})
|
c.JSON(http.StatusBadRequest, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "id",
|
||||||
|
"key": "server.validation.no_user_id_provided",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if user.ID != jwtUserID && userRole < constants.Roles.Editor {
|
if user.ID != jwtUserID && userRole < constants.Roles.Editor {
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized to update this user"})
|
c.JSON(http.StatusForbidden, gin.H{"errors": []gin.H{{
|
||||||
return
|
"field": "general",
|
||||||
}
|
"key": "server.error.unauthorized_update",
|
||||||
if user.Membership.SubscriptionModel.Name == "" {
|
}}})
|
||||||
logger.Error.Printf("No subscription model provided: %v", user.Email)
|
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": "No subscription model provided"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedModel, err := uc.MembershipService.GetModelByName(&user.Membership.SubscriptionModel.Name)
|
selectedModel, err := uc.MembershipService.GetModelByName(&user.Membership.SubscriptionModel.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("%v:No subscription model found: %#v", user.Email, err)
|
logger.Error.Printf("%v:No subscription model found: %#v", user.Email, err)
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Not a valid subscription model"})
|
c.JSON(http.StatusNotFound, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "subscription_model",
|
||||||
|
"key": "server.validation.invalid_subscription_model",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.Membership.SubscriptionModel = *selectedModel
|
user.Membership.SubscriptionModel = *selectedModel
|
||||||
@@ -84,21 +115,29 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
|
|||||||
// user.Email = existingUser.Email
|
// user.Email = existingUser.Email
|
||||||
// user.RoleID = existingUser.RoleID
|
// user.RoleID = existingUser.RoleID
|
||||||
// }
|
// }
|
||||||
|
|
||||||
updatedUser, err := uc.Service.UpdateUser(&user, userRole)
|
updatedUser, err := uc.Service.UpdateUser(&user, userRole)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case errors.ErrUserNotFound:
|
case errors.ErrUserNotFound:
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
c.JSON(http.StatusNotFound, gin.H{"errors": []gin.H{{
|
||||||
|
"field": user.FirstName + " " + user.LastName,
|
||||||
|
"key": "server.validation.user_not_found",
|
||||||
|
}}})
|
||||||
case errors.ErrInvalidUserData:
|
case errors.ErrInvalidUserData:
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user data"})
|
c.JSON(http.StatusBadRequest, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "user",
|
||||||
|
"key": "server.validation.invalid_user_data",
|
||||||
|
}}})
|
||||||
default:
|
default:
|
||||||
logger.Error.Printf("Failed to update user: %v", err)
|
logger.Error.Printf("Failed to update user: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server error"})
|
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
|
||||||
}
|
"field": "general",
|
||||||
|
"key": "server.error.internal_server_error",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
c.JSON(http.StatusAccepted, gin.H{"message": "User updated successfully", "user": updatedUser})
|
c.JSON(http.StatusAccepted, gin.H{"message": "User updated successfully", "user": updatedUser})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,37 +145,51 @@ func (uc *UserController) CurrentUserHandler(c *gin.Context) {
|
|||||||
userIDInterface, ok := c.Get("user_id")
|
userIDInterface, ok := c.Get("user_id")
|
||||||
if !ok || userIDInterface == nil {
|
if !ok || userIDInterface == nil {
|
||||||
logger.Error.Printf("Error getting user_id from header")
|
logger.Error.Printf("Error getting user_id from header")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Missing or invalid user ID type"})
|
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.validation.no_user_id_provided",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userID, ok := userIDInterface.(uint)
|
userID, ok := userIDInterface.(uint)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Error.Printf("Error: user_id is not of type uint")
|
logger.Error.Printf("Error: user_id is not of type uint")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"})
|
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "user",
|
||||||
|
"key": "server.error.internal_server_error",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := uc.Service.GetUserByID(uint(userID))
|
user, err := uc.Service.GetUserByID(uint(userID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("Error retrieving valid user: %v", err)
|
logger.Error.Printf("Error retrieving valid user: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving user."})
|
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.internal_server_error",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptions, err := uc.MembershipService.GetSubscriptions(nil)
|
subscriptions, err := uc.MembershipService.GetSubscriptions(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("Error retrieving subscriptions: %v", err)
|
logger.Error.Printf("Error retrieving subscriptions: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving subscriptions."})
|
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "validation.internal_server_error",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
licenceCategories, err := uc.DriversLicenceService.GetAllCategories()
|
licenceCategories, err := uc.DriversLicenceService.GetAllCategories()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("Error retrieving licence categories: %v", err)
|
logger.Error.Printf("Error retrieving licence categories: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving licence categories."})
|
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "validation.internal_server_error",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Error.Printf("licenceCategories: %#v", licenceCategories)
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"user": user.Safe(),
|
"user": user.Safe(),
|
||||||
"subscriptions": subscriptions,
|
"subscriptions": subscriptions,
|
||||||
@@ -164,14 +217,20 @@ func (uc *UserController) LoginHandler(c *gin.Context) {
|
|||||||
|
|
||||||
if err := c.ShouldBindJSON(&input); err != nil {
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
logger.Error.Printf("Couldn't decode input: %v", err.Error())
|
logger.Error.Printf("Couldn't decode input: %v", err.Error())
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Couldn't decode request data"})
|
c.JSON(http.StatusBadRequest, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.invalid_json",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := uc.Service.GetUserByEmail(input.Email)
|
user, err := uc.Service.GetUserByEmail(input.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("Error during user(%v) retrieval: %v\n", input.Email, err)
|
logger.Error.Printf("Error during user(%v) retrieval: %v\n", input.Email, err)
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Couldn't find user"})
|
c.JSON(http.StatusNotFound, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "login",
|
||||||
|
"key": "server.validation.user_not_found_or_wrong_password",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,20 +238,29 @@ func (uc *UserController) LoginHandler(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
logger.Error.Printf("Error during Password comparison: %v", err.Error())
|
logger.Error.Printf("Error during Password comparison: %v", err.Error())
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "couldn't calculate match"})
|
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.internal_server_error",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
||||||
logger.Error.Printf("Wrong Password: %v %v", user.FirstName, user.LastName)
|
logger.Error.Printf("Wrong Password: %v %v", user.FirstName, user.LastName)
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": "Wrong Password"})
|
c.JSON(http.StatusNotAcceptable, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "login",
|
||||||
|
"key": "server.validation.user_not_found_or_wrong_password",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error.Printf("jwtsevret: %v", config.Auth.JWTSecret)
|
logger.Error.Printf("jwtsevret: %v", config.Auth.JWTSecret)
|
||||||
token, err := middlewares.GenerateToken(config.Auth.JWTSecret, user, "")
|
token, err := middlewares.GenerateToken(config.Auth.JWTSecret, user, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate JWT token"})
|
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.jwt_generation_failed",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,18 +277,31 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
|
|||||||
|
|
||||||
if err := c.ShouldBindJSON(®Data); err != nil {
|
if err := c.ShouldBindJSON(®Data); err != nil {
|
||||||
logger.Error.Printf("Couldn't decode Userdata: %v", err)
|
logger.Error.Printf("Couldn't decode Userdata: %v", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Couldn't decode userdata"})
|
var validationErrors []gin.H
|
||||||
return
|
if ve, ok := err.(validator.ValidationErrors); ok {
|
||||||
}
|
for _, e := range ve {
|
||||||
if regData.User.Membership.SubscriptionModel.Name == "" {
|
validationErrors = append(validationErrors, gin.H{
|
||||||
logger.Error.Printf("No subscription model provided: %v", regData.User.Email)
|
"field": e.Field(),
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": "No subscription model provided"})
|
"key": "server.validation." + e.Tag(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validationErrors = append(validationErrors, gin.H{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.invalid_json",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": validationErrors})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedModel, err := uc.MembershipService.GetModelByName(®Data.User.Membership.SubscriptionModel.Name)
|
selectedModel, err := uc.MembershipService.GetModelByName(®Data.User.Membership.SubscriptionModel.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("%v:No subscription model found: %#v", regData.User.Email, err)
|
logger.Error.Printf("%v:No subscription model found: %#v", regData.User.Email, err)
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Not a valid subscription model"})
|
c.JSON(http.StatusNotFound, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "subscription_model",
|
||||||
|
"key": "server.validation.invalid_subscription_model",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
regData.User.Membership.SubscriptionModel = *selectedModel
|
regData.User.Membership.SubscriptionModel = *selectedModel
|
||||||
@@ -231,7 +312,18 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
|
|||||||
id, token, err := uc.Service.RegisterUser(®Data.User)
|
id, token, err := uc.Service.RegisterUser(®Data.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("Couldn't register User(%v): %v", regData.User.Email, err)
|
logger.Error.Printf("Couldn't register User(%v): %v", regData.User.Email, err)
|
||||||
c.JSON(int(id), gin.H{"error": "Couldn't register User"})
|
if strings.Contains(err.Error(), "UNIQUE constraint failed: users.email") {
|
||||||
|
c.JSON(http.StatusConflict, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "email",
|
||||||
|
"key": "server.validation.email_already_registered",
|
||||||
|
}}})
|
||||||
|
} else {
|
||||||
|
logger.Error.Printf("Failed to register user: %v", err)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.internal_server_error",
|
||||||
|
}}})
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
regData.User.ID = id
|
regData.User.ID = id
|
||||||
@@ -255,7 +347,10 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
|
|||||||
_, err = uc.ConsentService.RegisterConsent(&consent)
|
_, err = uc.ConsentService.RegisterConsent(&consent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("%v, Couldn't register consent: %v", regData.User.Email, err)
|
logger.Error.Printf("%v, Couldn't register consent: %v", regData.User.Email, err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Couldn't register User-consent"})
|
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.internal_server_error",
|
||||||
|
}}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Co
|
|||||||
expectedUserMail string
|
expectedUserMail string
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
expectNewCookie bool
|
expectNewCookie bool
|
||||||
|
expectedErrors []map[string]string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "With valid cookie",
|
name: "With valid cookie",
|
||||||
@@ -274,6 +275,9 @@ func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Co
|
|||||||
name: "Without cookie",
|
name: "Without cookie",
|
||||||
setupCookie: func(req *http.Request) {},
|
setupCookie: func(req *http.Request) {},
|
||||||
expectedStatus: http.StatusUnauthorized,
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
expectedErrors: []map[string]string{
|
||||||
|
{"field": "general", "key": "server.error.no_auth_token"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "With invalid cookie",
|
name: "With invalid cookie",
|
||||||
@@ -281,6 +285,9 @@ func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Co
|
|||||||
req.AddCookie(&invalidCookie)
|
req.AddCookie(&invalidCookie)
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusUnauthorized,
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
expectedErrors: []map[string]string{
|
||||||
|
{"field": "general", "key": "server.error.no_auth_token"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,12 +334,22 @@ func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Co
|
|||||||
assert.Nil(t, newCookie, "No new cookie should be set for non-expired token")
|
assert.Nil(t, newCookie, "No new cookie should be set for non-expired token")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For unauthorized requests, check for an error message
|
// For unauthorized requests, check for the new error structure
|
||||||
var errorResponse map[string]string
|
var errorResponse map[string]interface{}
|
||||||
err := json.Unmarshal(w.Body.Bytes(), &errorResponse)
|
err := json.Unmarshal(w.Body.Bytes(), &errorResponse)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, errorResponse, "error")
|
|
||||||
assert.NotEmpty(t, errorResponse["error"])
|
errors, ok := errorResponse["errors"].([]interface{})
|
||||||
|
assert.True(t, ok, "Expected 'errors' field in response")
|
||||||
|
assert.Len(t, errors, len(tt.expectedErrors), "Unexpected number of errors")
|
||||||
|
|
||||||
|
for i, expectedError := range tt.expectedErrors {
|
||||||
|
if i < len(errors) {
|
||||||
|
actualError := errors[i].(map[string]interface{})
|
||||||
|
assert.Equal(t, expectedError["field"], actualError["field"], "Mismatched error field")
|
||||||
|
assert.Equal(t, expectedError["key"], actualError["key"], "Mismatched error key")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -414,7 +431,7 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
setupCookie func(*http.Request)
|
setupCookie func(*http.Request)
|
||||||
updateFunc func(*models.User)
|
updateFunc func(*models.User)
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
expectedError string
|
expectedErrors []map[string]string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Valid Update",
|
name: "Valid Update",
|
||||||
@@ -441,7 +458,9 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
u.Phone = "01738484994"
|
u.Phone = "01738484994"
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusUnauthorized,
|
expectedStatus: http.StatusUnauthorized,
|
||||||
expectedError: "Auth token invalid",
|
expectedErrors: []map[string]string{
|
||||||
|
{"field": "general", "key": "server.error.no_auth_token"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid Email Update",
|
name: "Invalid Email Update",
|
||||||
@@ -456,7 +475,9 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
u.Email = "invalid-email"
|
u.Email = "invalid-email"
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusBadRequest,
|
||||||
expectedError: "Invalid user data",
|
expectedErrors: []map[string]string{
|
||||||
|
{"field": "Email", "key": "server.validation.email"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Change LicenceNumber",
|
name: "Change LicenceNumber",
|
||||||
@@ -491,7 +512,7 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
expectedStatus: http.StatusAccepted,
|
expectedStatus: http.StatusAccepted,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Add 2 categories",
|
name: "Delete 1 and add 1 category",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
req.AddCookie(&loginCookie)
|
req.AddCookie(&loginCookie)
|
||||||
},
|
},
|
||||||
@@ -502,13 +523,31 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
u.Phone = "01738484994"
|
u.Phone = "01738484994"
|
||||||
u.DriversLicence.LicenceNumber = "B072RRE2I50"
|
u.DriversLicence.LicenceNumber = "B072RRE2I50"
|
||||||
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
|
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
|
||||||
category, err := licenceRepo.FindCategoryByName("B")
|
category, err := licenceRepo.FindCategoryByName("A")
|
||||||
category2, err := licenceRepo.FindCategoryByName("BE")
|
category2, err := licenceRepo.FindCategoryByName("BE")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
u.DriversLicence.LicenceCategories = []models.LicenceCategory{category, category2}
|
u.DriversLicence.LicenceCategories = []models.LicenceCategory{category, category2}
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusAccepted,
|
expectedStatus: http.StatusAccepted,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Delete 1 category",
|
||||||
|
setupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(&loginCookie)
|
||||||
|
},
|
||||||
|
updateFunc: func(u *models.User) {
|
||||||
|
u.Password = ""
|
||||||
|
u.FirstName = "John Updated"
|
||||||
|
u.LastName = "Doe Updated"
|
||||||
|
u.Phone = "01738484994"
|
||||||
|
u.DriversLicence.LicenceNumber = "B072RRE2I50"
|
||||||
|
var licenceRepo repositories.DriversLicenceInterface = &repositories.DriversLicenceRepository{}
|
||||||
|
category, err := licenceRepo.FindCategoryByName("A")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
u.DriversLicence.LicenceCategories = []models.LicenceCategory{category}
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusAccepted,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Delete all categories",
|
name: "Delete all categories",
|
||||||
setupCookie: func(req *http.Request) {
|
setupCookie: func(req *http.Request) {
|
||||||
@@ -538,7 +577,9 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
u.FirstName = "John Missing ID"
|
u.FirstName = "John Missing ID"
|
||||||
},
|
},
|
||||||
expectedStatus: http.StatusForbidden,
|
expectedStatus: http.StatusForbidden,
|
||||||
expectedError: "You are not authorized to update this user",
|
expectedErrors: []map[string]string{
|
||||||
|
{"field": "general", "key": "server.error.unauthorized_update"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Password Update",
|
name: "Password Update",
|
||||||
@@ -582,7 +623,7 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
t.Fatalf("Failed to marshal user data: %v", err)
|
t.Fatalf("Failed to marshal user data: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error.Printf("Updated User: %#v", updatedUser)
|
// logger.Error.Printf("Updated User: %#v", updatedUser)
|
||||||
// Create request
|
// Create request
|
||||||
req, _ := http.NewRequest("PUT", "/users/"+strconv.FormatUint(uint64(user.ID), 10), bytes.NewBuffer(jsonData))
|
req, _ := http.NewRequest("PUT", "/users/"+strconv.FormatUint(uint64(user.ID), 10), bytes.NewBuffer(jsonData))
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
@@ -599,18 +640,38 @@ func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
|||||||
// Perform request
|
// Perform request
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
bodyBytes, _ := io.ReadAll(w.Body)
|
||||||
|
t.Logf("Response Body: %s", string(bodyBytes))
|
||||||
// Check status code
|
// Check status code
|
||||||
assert.Equal(t, tt.expectedStatus, w.Code)
|
assert.Equal(t, tt.expectedStatus, w.Code)
|
||||||
|
|
||||||
// Parse response
|
// Parse response
|
||||||
var response map[string]interface{}
|
var response map[string]interface{}
|
||||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
err = json.Unmarshal(bodyBytes, &response)
|
||||||
assert.NoError(t, err)
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to unmarshal response body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if tt.expectedError != "" {
|
if tt.expectedErrors != nil {
|
||||||
assert.Equal(t, tt.expectedError, response["error"])
|
errors, ok := response["errors"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected 'errors' field in response, got: %v", response)
|
||||||
|
}
|
||||||
|
assert.Len(t, errors, len(tt.expectedErrors), "Unexpected number of errors")
|
||||||
|
for i, expectedError := range tt.expectedErrors {
|
||||||
|
if i < len(errors) {
|
||||||
|
actualError := errors[i].(map[string]interface{})
|
||||||
|
assert.Equal(t, expectedError["field"], actualError["field"], "Mismatched error field")
|
||||||
|
assert.Equal(t, expectedError["key"], actualError["key"], "Mismatched error key")
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, "User updated successfully", response["message"])
|
// Check for success message
|
||||||
|
message, ok := response["message"].(string)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected 'message' field in response, got: %v", response)
|
||||||
|
}
|
||||||
|
assert.Equal(t, "User updated successfully", message)
|
||||||
|
|
||||||
// Verify the update in the database
|
// Verify the update in the database
|
||||||
updatedUserFromDB, err := Uc.Service.GetUserByID(user.ID)
|
updatedUserFromDB, err := Uc.Service.GetUserByID(user.ID)
|
||||||
@@ -844,7 +905,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
return []RegisterUserTest{
|
return []RegisterUserTest{
|
||||||
{
|
{
|
||||||
Name: "birthday < 18 should fail",
|
Name: "birthday < 18 should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -854,7 +915,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "FirstName empty, should fail",
|
Name: "FirstName empty, should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -864,7 +925,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "LastName Empty should fail",
|
Name: "LastName Empty should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -874,7 +935,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "EMail wrong format should fail",
|
Name: "EMail wrong format should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "johnexample.com"},
|
WantDBData: map[string]interface{}{"email": "johnexample.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -884,7 +945,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Missing Zip Code should fail",
|
Name: "Missing Zip Code should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -894,7 +955,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Missing Address should fail",
|
Name: "Missing Address should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -904,7 +965,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Missing City should fail",
|
Name: "Missing City should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -914,7 +975,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Missing IBAN should fail",
|
Name: "Missing IBAN should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -924,7 +985,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Invalid IBAN should fail",
|
Name: "Invalid IBAN should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -934,7 +995,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Missing subscription plan should fail",
|
Name: "Missing subscription plan should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -944,7 +1005,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Invalid subscription plan should fail",
|
Name: "Invalid subscription plan should fail",
|
||||||
WantResponse: http.StatusNotFound,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -982,7 +1043,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Subscription constraints not entered; should fail",
|
Name: "Subscription constraints not entered; should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.junior.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.junior.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -993,7 +1054,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Subscription constraints wrong; should fail",
|
Name: "Subscription constraints wrong; should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.junior.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.junior.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
@@ -1028,7 +1089,7 @@ func getTestUsers() []RegisterUserTest {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "wrong driverslicence number, should fail",
|
Name: "wrong driverslicence number, should fail",
|
||||||
WantResponse: http.StatusNotAcceptable,
|
WantResponse: http.StatusBadRequest,
|
||||||
WantDBData: map[string]interface{}{"email": "john.wronglicence.doe@example.com"},
|
WantDBData: map[string]interface{}{"email": "john.wronglicence.doe@example.com"},
|
||||||
Assert: false,
|
Assert: false,
|
||||||
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
Input: GenerateInputJSON(customizeInput(func(user models.User) models.User {
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func createLicenceCategories() []models.LicenceCategory {
|
|||||||
{Category: "CE"},
|
{Category: "CE"},
|
||||||
{Category: "D1E"},
|
{Category: "D1E"},
|
||||||
{Category: "DE"},
|
{Category: "DE"},
|
||||||
{Category: "R"},
|
{Category: "T"},
|
||||||
{Category: "L"},
|
{Category: "L"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,16 +142,12 @@ func createAdmin(userMail string, subscriptionModelID uint) (*models.User, error
|
|||||||
StartDate: time.Now(),
|
StartDate: time.Now(),
|
||||||
SubscriptionModelID: subscriptionModelID,
|
SubscriptionModelID: subscriptionModelID,
|
||||||
},
|
},
|
||||||
BankAccount: models.BankAccount{
|
BankAccount: models.BankAccount{},
|
||||||
AccountHolderName: "",
|
|
||||||
Bank: "",
|
|
||||||
IBAN: "", //"DE49700500000008447644", //fake
|
|
||||||
},
|
|
||||||
DriversLicence: models.DriversLicence{
|
DriversLicence: models.DriversLicence{
|
||||||
Status: constants.UnverifiedStatus,
|
Status: constants.UnverifiedStatus,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
//"DE49700500000008447644", //fake
|
||||||
}
|
}
|
||||||
|
|
||||||
func Close() error {
|
func Close() error {
|
||||||
|
|||||||
@@ -78,7 +78,11 @@ func AuthMiddleware() gin.HandlerFunc {
|
|||||||
tokenString, err := c.Cookie("jwt")
|
tokenString, err := c.Cookie("jwt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("No Auth token: %v\n", err)
|
logger.Error.Printf("No Auth token: %v\n", err)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "No Auth token"})
|
c.JSON(http.StatusUnauthorized,
|
||||||
|
gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.no_auth_token",
|
||||||
|
}}})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -91,7 +95,11 @@ func AuthMiddleware() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Error.Printf("Token(%v) is invalid: %v\n", tokenString, err)
|
logger.Error.Printf("Token(%v) is invalid: %v\n", tokenString, err)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Auth token invalid"})
|
c.JSON(http.StatusUnauthorized,
|
||||||
|
gin.H{"errors": []gin.H{{
|
||||||
|
"field": "general",
|
||||||
|
"key": "server.error.no_auth_token",
|
||||||
|
}}})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ type BankAccount struct {
|
|||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
MandateDateSigned time.Time `gorm:"not null" json:"mandate_date_signed"`
|
MandateDateSigned time.Time `gorm:"not null" json:"mandate_date_signed"`
|
||||||
Bank string `json:"bank_name" validate:"omitempty,alphanumunicode,safe_content"`
|
Bank string `json:"bank_name" binding:"omitempty,alphanumunicode,safe_content"`
|
||||||
AccountHolderName string `json:"account_holder_name" validate:"omitempty,alphaunicode,safe_content"`
|
AccountHolderName string `json:"account_holder_name" binding:"omitempty,alphaunicode,safe_content"`
|
||||||
IBAN string `gorm:"not null" json:"iban" validate:"iban"`
|
IBAN string `gorm:"not null" json:"iban" binding:"iban"`
|
||||||
BIC string `json:"bic" validate:"omitempty,bic"`
|
BIC string `json:"bic" binding:"omitempty,bic"`
|
||||||
MandateReference string `gorm:"not null" json:"mandate_reference"`
|
MandateReference string `gorm:"not null" json:"mandate_reference"`
|
||||||
ID uint `gorm:"primaryKey"`
|
ID uint `gorm:"primaryKey"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
type Consent struct {
|
type Consent struct {
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
FirstName string `gorm:"not null" json:"first_name" validate:"safe_content"`
|
FirstName string `gorm:"not null" json:"first_name" binding:"safe_content"`
|
||||||
LastName string `gorm:"not null" json:"last_name" validate:"safe_content"`
|
LastName string `gorm:"not null" json:"last_name" binding:"safe_content"`
|
||||||
Email string `json:"email" validate:"email,safe_content"`
|
Email string `json:"email" binding:"email,safe_content"`
|
||||||
ConsentType string `gorm:"not null" json:"consent_type" validate:"safe_content"`
|
ConsentType string `gorm:"not null" json:"consent_type" binding:"safe_content"`
|
||||||
ID uint `gorm:"primaryKey"`
|
ID uint `gorm:"primaryKey"`
|
||||||
User User
|
User User
|
||||||
UserID uint
|
UserID uint
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import (
|
|||||||
|
|
||||||
type DriversLicence struct {
|
type DriversLicence struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey"`
|
ID uint `json:"id" gorm:"primaryKey"`
|
||||||
Status int8 `json:"status" validate:"omitempty,number"`
|
Status int8 `json:"status" binding:"omitempty,number"`
|
||||||
LicenceNumber string `json:"number" validate:"omitempty,euDriversLicence,safe_content"`
|
LicenceNumber string `json:"number" binding:"omitempty,euDriversLicence,safe_content"`
|
||||||
IssuedDate time.Time `json:"issued_date" validate:"omitempty,lte"`
|
IssuedDate time.Time `json:"issued_date" binding:"omitempty,lte"`
|
||||||
ExpirationDate time.Time `json:"expiration_date" validate:"omitempty,gt"`
|
ExpirationDate time.Time `json:"expiration_date" binding:"omitempty,gt"`
|
||||||
IssuingCountry string `json:"country" validate:"safe_content"`
|
IssuingCountry string `json:"country" binding:"safe_content"`
|
||||||
LicenceCategories []LicenceCategory `json:"licence_categories" gorm:"many2many:licence_2_categories"`
|
LicenceCategories []LicenceCategory `json:"licence_categories" gorm:"many2many:licence_2_categories"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LicenceCategory struct {
|
type LicenceCategory struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey"`
|
ID uint `json:"id" gorm:"primaryKey"`
|
||||||
Category string `json:"category" validate:"safe_content"`
|
Category string `json:"category" binding:"safe_content"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ type Membership struct {
|
|||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
StartDate time.Time `json:"start_date"`
|
StartDate time.Time `json:"start_date"`
|
||||||
EndDate time.Time `json:"end_date"`
|
EndDate time.Time `json:"end_date"`
|
||||||
Status int8 `json:"status" validate:"safe_content"`
|
Status int8 `json:"status" binding:"number,safe_content"`
|
||||||
SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID" json:"subscription_model"`
|
SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID" json:"subscription_model"`
|
||||||
SubscriptionModelID uint `json:"subsription_model_id"`
|
SubscriptionModelID uint `json:"subsription_model_id"`
|
||||||
ParentMembershipID uint `json:"parent_member_id" validate:"omitempty,omitnil,number"`
|
ParentMembershipID uint `json:"parent_member_id" binding:"omitempty,omitnil,number"`
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import (
|
|||||||
type SubscriptionModel struct {
|
type SubscriptionModel struct {
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
Name string `gorm:"unique" json:"name" validate:"required,subscriptionModel,safe_content"`
|
Name string `gorm:"unique" json:"name" binding:"required"`
|
||||||
Details string `json:"details" validate:"required"`
|
Details string `json:"details"`
|
||||||
Conditions string `json:"conditions"`
|
Conditions string `json:"conditions"`
|
||||||
RequiredMembershipField string `json:"required_membership_field" validate:"membershipField"`
|
RequiredMembershipField string `json:"required_membership_field"`
|
||||||
ID uint `json:"id" gorm:"primaryKey"`
|
ID uint `json:"id" gorm:"primaryKey"`
|
||||||
MonthlyFee float32 `json:"monthly_fee" validate:"number,gte=0"`
|
MonthlyFee float32 `json:"monthly_fee"`
|
||||||
HourlyRate float32 `json:"hourly_rate" validate:"number,gte=0"`
|
HourlyRate float32 `json:"hourly_rate"`
|
||||||
IncludedPerYear int16 `json:"included_hours_per_year" validate:"omitempty,number,gte=0"`
|
IncludedPerYear int16 `json:"included_hours_per_year"`
|
||||||
IncludedPerMonth int16 `json:"included_hours_per_month" validate:"omitempty,number,gte=0"`
|
IncludedPerMonth int16 `json:"included_hours_per_month"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,18 @@ import (
|
|||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
DateOfBirth time.Time `gorm:"not null" json:"date_of_birth" validate:"required,age"`
|
DateOfBirth time.Time `gorm:"not null" json:"date_of_birth" binding:"required,safe_content"`
|
||||||
Company string `json:"company" validate:"omitempty,omitnil,safe_content"`
|
Company string `json:"company" binding:"omitempty,omitnil,safe_content"`
|
||||||
Phone string `json:"phone" validate:"omitempty,omitnil,safe_content"`
|
Phone string `json:"phone" binding:"omitempty,omitnil,safe_content"`
|
||||||
Notes string `json:"notes" validate:"safe_content"`
|
Notes string `json:"notes" binding:"safe_content"`
|
||||||
FirstName string `gorm:"not null" json:"first_name" validate:"required,safe_content"`
|
FirstName string `gorm:"not null" json:"first_name" binding:"required,safe_content"`
|
||||||
Password string `json:"password" validate:"required_unless=RoleID 0,safe_content"`
|
Password string `json:"password" binding:"required_unless=RoleID 0,safe_content"`
|
||||||
Email string `gorm:"unique;not null" json:"email" validate:"required,email,safe_content"`
|
Email string `gorm:"unique;not null" json:"email" binding:"required,email,safe_content"`
|
||||||
LastName string `gorm:"not null" json:"last_name" validate:"required,safe_content"`
|
LastName string `gorm:"not null" json:"last_name" binding:"required,safe_content"`
|
||||||
ProfilePicture string `json:"profile_picture" validate:"omitempty,omitnil,image,safe_content"`
|
ProfilePicture string `json:"profile_picture" binding:"omitempty,omitnil,image,safe_content"`
|
||||||
Address string `gorm:"not null" json:"address" validate:"required,safe_content"`
|
Address string `gorm:"not null" json:"address" binding:"required,safe_content"`
|
||||||
ZipCode string `gorm:"not null" json:"zip_code" validate:"required,alphanum,safe_content"`
|
ZipCode string `gorm:"not null" json:"zip_code" binding:"required,alphanum,safe_content"`
|
||||||
City string `form:"not null" json:"city" validate:"required,alphaunicode,safe_content"`
|
City string `form:"not null" json:"city" binding:"required,alphaunicode,safe_content"`
|
||||||
Consents []Consent `gorm:"constraint:OnUpdate:CASCADE"`
|
Consents []Consent `gorm:"constraint:OnUpdate:CASCADE"`
|
||||||
BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bank_account"`
|
BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bank_account"`
|
||||||
BankAccountID uint
|
BankAccountID uint
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
type SubscriptionModelsRepositoryInterface interface {
|
type SubscriptionModelsRepositoryInterface interface {
|
||||||
CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (uint, error)
|
CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (uint, error)
|
||||||
GetMembershipModelNames() ([]string, error)
|
GetMembershipModelNames() ([]string, error)
|
||||||
GetModelByName(modelname *string) (*models.SubscriptionModel, error)
|
|
||||||
GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error)
|
GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ func (sr *SubscriptionModelsRepository) CreateSubscriptionModel(subscriptionMode
|
|||||||
return subscriptionModel.ID, nil
|
return subscriptionModel.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sr *SubscriptionModelsRepository) GetModelByName(modelname *string) (*models.SubscriptionModel, error) {
|
func GetModelByName(modelname *string) (*models.SubscriptionModel, error) {
|
||||||
var model models.SubscriptionModel
|
var model models.SubscriptionModel
|
||||||
if err := database.DB.Where("name = ?", modelname).First(&model).Error; err != nil {
|
if err := database.DB.Where("name = ?", modelname).First(&model).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import (
|
|||||||
|
|
||||||
"GoMembership/internal/models"
|
"GoMembership/internal/models"
|
||||||
"GoMembership/pkg/errors"
|
"GoMembership/pkg/errors"
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserRepositoryInterface interface {
|
type UserRepositoryInterface interface {
|
||||||
CreateUser(user *models.User) (uint, error)
|
CreateUser(user *models.User) (uint, error)
|
||||||
UpdateUser(user *models.User) (*models.User, error)
|
UpdateUser(user *models.User) (*models.User, error)
|
||||||
GetUsers(where map[string]interface{}) (*[]models.User, error)
|
GetUsers(where map[string]interface{}) (*[]models.User, error)
|
||||||
GetUserByID(userID *uint) (*models.User, error)
|
|
||||||
GetUserByEmail(email string) (*models.User, error)
|
GetUserByEmail(email string) (*models.User, error)
|
||||||
SetVerificationToken(verification *models.Verification) (uint, error)
|
SetVerificationToken(verification *models.Verification) (uint, error)
|
||||||
IsVerified(userID *uint) (bool, error)
|
IsVerified(userID *uint) (bool, error)
|
||||||
@@ -28,6 +28,7 @@ type UserRepository struct{}
|
|||||||
func (ur *UserRepository) CreateUser(user *models.User) (uint, error) {
|
func (ur *UserRepository) CreateUser(user *models.User) (uint, error) {
|
||||||
result := database.DB.Create(user)
|
result := database.DB.Create(user)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
|
logger.Error.Printf("Create User error: %#v", result.Error)
|
||||||
return 0, result.Error
|
return 0, result.Error
|
||||||
}
|
}
|
||||||
return user.ID, nil
|
return user.ID, nil
|
||||||
@@ -39,10 +40,14 @@ func (ur *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := database.DB.Transaction(func(tx *gorm.DB) error {
|
err := database.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
if err := tx.First(&models.User{}, user.ID).Error; err != nil {
|
// Check if the user exists in the database
|
||||||
|
var existingUser models.User
|
||||||
|
if err := tx.Preload("DriversLicence.LicenceCategories").
|
||||||
|
Preload("Membership").
|
||||||
|
First(&existingUser, user.ID).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Update the user's main fields
|
||||||
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(user)
|
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(user)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result.Error
|
return result.Error
|
||||||
@@ -50,6 +55,29 @@ func (ur *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
|
|||||||
if result.RowsAffected == 0 {
|
if result.RowsAffected == 0 {
|
||||||
return errors.ErrNoRowsAffected
|
return errors.ErrNoRowsAffected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle the update of the LicenceCategories explicitly
|
||||||
|
if user.DriversLicence.ID != 0 {
|
||||||
|
// Replace the LicenceCategories with the new list
|
||||||
|
if err := tx.Model(&existingUser.DriversLicence).Association("LicenceCategories").Replace(user.DriversLicence.LicenceCategories); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the Membership if provided
|
||||||
|
if user.Membership.ID != 0 {
|
||||||
|
if err := tx.Model(&existingUser.Membership).Updates(user.Membership).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the DriversLicence fields if provided
|
||||||
|
if user.DriversLicence.ID != 0 {
|
||||||
|
if err := tx.Model(&existingUser.DriversLicence).Updates(user.DriversLicence).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -58,10 +86,11 @@ func (ur *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var updatedUser models.User
|
var updatedUser models.User
|
||||||
if err := database.DB.First(&updatedUser, user.ID).Error; err != nil {
|
if err := database.DB.Preload("DriversLicence.LicenceCategories").
|
||||||
|
Preload("Membership").
|
||||||
|
First(&updatedUser, user.ID).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &updatedUser, nil
|
return &updatedUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +110,7 @@ func (ur *UserRepository) GetUsers(where map[string]interface{}) (*[]models.User
|
|||||||
return &users, nil
|
return &users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur *UserRepository) GetUserByID(userID *uint) (*models.User, error) {
|
func GetUserByID(userID *uint) (*models.User, error) {
|
||||||
var user models.User
|
var user models.User
|
||||||
result := database.DB.
|
result := database.DB.
|
||||||
Preload(clause.Associations).
|
Preload(clause.Associations).
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"GoMembership/internal/controllers"
|
"GoMembership/internal/controllers"
|
||||||
"GoMembership/internal/middlewares"
|
"GoMembership/internal/middlewares"
|
||||||
"GoMembership/internal/repositories"
|
"GoMembership/internal/repositories"
|
||||||
|
"GoMembership/internal/validation"
|
||||||
|
|
||||||
"GoMembership/internal/routes"
|
"GoMembership/internal/routes"
|
||||||
"GoMembership/internal/services"
|
"GoMembership/internal/services"
|
||||||
@@ -63,6 +64,7 @@ func Run() {
|
|||||||
router.Use(middlewares.RateLimitMiddleware(limiter))
|
router.Use(middlewares.RateLimitMiddleware(limiter))
|
||||||
|
|
||||||
routes.RegisterRoutes(router, userController, membershipController, contactController)
|
routes.RegisterRoutes(router, userController, membershipController, contactController)
|
||||||
|
validation.SetupValidators()
|
||||||
|
|
||||||
logger.Info.Println("Starting server on :8080")
|
logger.Info.Println("Starting server on :8080")
|
||||||
srv = &http.Server{
|
srv = &http.Server{
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"slices"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
|
|
||||||
"GoMembership/internal/models"
|
"GoMembership/internal/models"
|
||||||
"GoMembership/internal/repositories"
|
"GoMembership/internal/repositories"
|
||||||
"GoMembership/internal/utils"
|
|
||||||
"GoMembership/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MembershipServiceInterface interface {
|
type MembershipServiceInterface interface {
|
||||||
@@ -37,9 +32,6 @@ func (service *MembershipService) FindMembershipByUserID(userID uint) (*models.M
|
|||||||
|
|
||||||
// Membership_Subscriptions
|
// Membership_Subscriptions
|
||||||
func (service *MembershipService) RegisterSubscription(subscription *models.SubscriptionModel) (uint, error) {
|
func (service *MembershipService) RegisterSubscription(subscription *models.SubscriptionModel) (uint, error) {
|
||||||
if err := validateSubscriptionData(subscription); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return service.SubscriptionRepo.CreateSubscriptionModel(subscription)
|
return service.SubscriptionRepo.CreateSubscriptionModel(subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,15 +40,7 @@ func (service *MembershipService) GetMembershipModelNames() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (service *MembershipService) GetModelByName(modelname *string) (*models.SubscriptionModel, error) {
|
func (service *MembershipService) GetModelByName(modelname *string) (*models.SubscriptionModel, error) {
|
||||||
sModelNames, err := service.SubscriptionRepo.GetMembershipModelNames()
|
return repositories.GetModelByName(modelname)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !slices.Contains(sModelNames, *modelname) {
|
|
||||||
return nil, errors.ErrNotFound
|
|
||||||
}
|
|
||||||
return service.SubscriptionRepo.GetModelByName(modelname)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *MembershipService) GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error) {
|
func (service *MembershipService) GetSubscriptions(where map[string]interface{}) (*[]models.SubscriptionModel, error) {
|
||||||
@@ -65,12 +49,3 @@ func (service *MembershipService) GetSubscriptions(where map[string]interface{})
|
|||||||
}
|
}
|
||||||
return service.SubscriptionRepo.GetSubscriptions(where)
|
return service.SubscriptionRepo.GetSubscriptions(where)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSubscriptionData(subscription *models.SubscriptionModel) error {
|
|
||||||
validate := validator.New()
|
|
||||||
// subscriptionModel and membershipField don't have to be evaluated if adding a new subscription
|
|
||||||
validate.RegisterValidation("subscriptionModel", func(fl validator.FieldLevel) bool { return true })
|
|
||||||
validate.RegisterValidation("membershipField", func(fl validator.FieldLevel) bool { return true })
|
|
||||||
validate.RegisterValidation("safe_content", utils.ValidateSafeContent)
|
|
||||||
return validate.Struct(subscription)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
|
|
||||||
"github.com/alexedwards/argon2id"
|
"github.com/alexedwards/argon2id"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
@@ -34,12 +33,6 @@ type UserService struct {
|
|||||||
|
|
||||||
func (service *UserService) UpdateUser(user *models.User, userRole int8) (*models.User, error) {
|
func (service *UserService) UpdateUser(user *models.User, userRole int8) (*models.User, error) {
|
||||||
|
|
||||||
if err := validateUserData(user, userRole); err != nil {
|
|
||||||
logger.Info.Printf("UPDATING user: %#v", user)
|
|
||||||
logger.Error.Printf("Failed to validate user data: %v", err)
|
|
||||||
return nil, errors.ErrInvalidUserData
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Password != "" {
|
if user.Password != "" {
|
||||||
setPassword(user.Password, user)
|
setPassword(user.Password, user)
|
||||||
}
|
}
|
||||||
@@ -66,9 +59,6 @@ func (service *UserService) UpdateUser(user *models.User, userRole int8) (*model
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (service *UserService) RegisterUser(user *models.User) (uint, string, error) {
|
func (service *UserService) RegisterUser(user *models.User) (uint, string, error) {
|
||||||
if err := validateUserData(user, user.RoleID); err != nil {
|
|
||||||
return http.StatusNotAcceptable, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
setPassword(user.Password, user)
|
setPassword(user.Password, user)
|
||||||
|
|
||||||
@@ -76,21 +66,19 @@ func (service *UserService) RegisterUser(user *models.User) (uint, string, error
|
|||||||
user.CreatedAt = time.Now()
|
user.CreatedAt = time.Now()
|
||||||
user.UpdatedAt = time.Now()
|
user.UpdatedAt = time.Now()
|
||||||
user.PaymentStatus = constants.AwaitingPaymentStatus
|
user.PaymentStatus = constants.AwaitingPaymentStatus
|
||||||
// user.DriversLicence.Status = constants.UnverifiedStatus
|
user.DriversLicence.Status = constants.UnverifiedStatus
|
||||||
user.BankAccount.MandateDateSigned = time.Now()
|
user.BankAccount.MandateDateSigned = time.Now()
|
||||||
id, err := service.Repo.CreateUser(user)
|
id, err := service.Repo.CreateUser(user)
|
||||||
|
|
||||||
if err != nil && strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
if err != nil {
|
||||||
return http.StatusConflict, "", err
|
return 0, "", err
|
||||||
} else if err != nil {
|
|
||||||
return http.StatusInternalServerError, "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user.ID = id
|
user.ID = id
|
||||||
|
|
||||||
token, err := utils.GenerateVerificationToken()
|
token, err := utils.GenerateVerificationToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, "", err
|
return 0, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info.Printf("TOKEN: %v", token)
|
logger.Info.Printf("TOKEN: %v", token)
|
||||||
@@ -98,10 +86,10 @@ func (service *UserService) RegisterUser(user *models.User) (uint, string, error
|
|||||||
// Check if user is already verified
|
// Check if user is already verified
|
||||||
verified, err := service.Repo.IsVerified(&user.ID)
|
verified, err := service.Repo.IsVerified(&user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, "", err
|
return 0, "", err
|
||||||
}
|
}
|
||||||
if verified {
|
if verified {
|
||||||
return http.StatusAlreadyReported, "", errors.ErrAlreadyVerified
|
return 0, "", errors.ErrAlreadyVerified
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the Verification record
|
// Prepare the Verification record
|
||||||
@@ -119,7 +107,7 @@ func (service *UserService) RegisterUser(user *models.User) (uint, string, error
|
|||||||
|
|
||||||
func (service *UserService) GetUserByID(id uint) (*models.User, error) {
|
func (service *UserService) GetUserByID(id uint) (*models.User, error) {
|
||||||
|
|
||||||
return service.Repo.GetUserByID(&id)
|
return repositories.GetUserByID(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *UserService) GetUserByEmail(email string) (*models.User, error) {
|
func (service *UserService) GetUserByEmail(email string) (*models.User, error) {
|
||||||
@@ -140,7 +128,7 @@ func (service *UserService) VerifyUser(token *string) (*models.User, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
user, err := service.Repo.GetUserByID(&verification.UserID)
|
user, err := repositories.GetUserByID(&verification.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -159,27 +147,6 @@ func (service *UserService) VerifyUser(token *string) (*models.User, error) {
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateUserData(user *models.User, userRole int8) error {
|
|
||||||
validate := validator.New()
|
|
||||||
validate.RegisterValidation("safe_content", utils.ValidateSafeContent)
|
|
||||||
if userRole == constants.Roles.Admin {
|
|
||||||
validate.RegisterValidation("membershipField", utils.ValidateToTrue)
|
|
||||||
validate.RegisterValidation("age", utils.ValidateToTrue)
|
|
||||||
validate.RegisterValidation("bic", utils.ValidateToTrue)
|
|
||||||
validate.RegisterValidation("subscriptionModel", utils.ValidateToTrue)
|
|
||||||
validate.RegisterValidation("iban", utils.ValidateToTrue)
|
|
||||||
validate.RegisterValidation("euDriversLicence", utils.ValidateToTrue)
|
|
||||||
} else {
|
|
||||||
validate.RegisterValidation("membershipField", utils.ValidateRequiredMembershipField)
|
|
||||||
validate.RegisterValidation("age", utils.AgeValidator)
|
|
||||||
validate.RegisterValidation("bic", utils.BICValidator)
|
|
||||||
validate.RegisterValidation("subscriptionModel", utils.SubscriptionModelValidator)
|
|
||||||
validate.RegisterValidation("iban", utils.IBANValidator)
|
|
||||||
validate.RegisterValidation("euDriversLicence", utils.ValidateDriversLicence)
|
|
||||||
}
|
|
||||||
return validate.Struct(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setPassword(plaintextPassword string, u *models.User) error {
|
func setPassword(plaintextPassword string, u *models.User) error {
|
||||||
hash, err := argon2id.CreateHash(plaintextPassword, argon2id.DefaultParams)
|
hash, err := argon2id.CreateHash(plaintextPassword, argon2id.DefaultParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
// import "regexp"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoMembership/internal/database"
|
|
||||||
"GoMembership/internal/models"
|
|
||||||
"GoMembership/pkg/logger"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/jbub/banking/iban"
|
|
||||||
"github.com/jbub/banking/swift"
|
|
||||||
)
|
|
||||||
|
|
||||||
var xssPatterns = []*regexp.Regexp{
|
|
||||||
regexp.MustCompile(`(?i)<script`),
|
|
||||||
regexp.MustCompile(`(?i)javascript:`),
|
|
||||||
regexp.MustCompile(`(?i)on\w+\s*=`),
|
|
||||||
regexp.MustCompile(`(?i)(vbscript|data):`),
|
|
||||||
regexp.MustCompile(`(?i)<(iframe|object|embed|applet)`),
|
|
||||||
regexp.MustCompile(`(?i)expression\s*\(`),
|
|
||||||
regexp.MustCompile(`(?i)url\s*\(`),
|
|
||||||
regexp.MustCompile(`(?i)<\?`),
|
|
||||||
regexp.MustCompile(`(?i)<%`),
|
|
||||||
regexp.MustCompile(`(?i)<!\[CDATA\[`),
|
|
||||||
regexp.MustCompile(`(?i)<(svg|animate)`),
|
|
||||||
regexp.MustCompile(`(?i)<(audio|video|source)`),
|
|
||||||
regexp.MustCompile(`(?i)base64`),
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateToTrue(fl validator.FieldLevel) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func AgeValidator(fl validator.FieldLevel) bool {
|
|
||||||
fieldValue := fl.Field()
|
|
||||||
dateOfBirth := fieldValue.Interface().(time.Time)
|
|
||||||
now := time.Now()
|
|
||||||
age := now.Year() - dateOfBirth.Year()
|
|
||||||
|
|
||||||
if now.YearDay() < dateOfBirth.YearDay() {
|
|
||||||
age-- // if birthday is in the future..
|
|
||||||
}
|
|
||||||
|
|
||||||
return age >= 18
|
|
||||||
}
|
|
||||||
|
|
||||||
func SubscriptionModelValidator(fl validator.FieldLevel) bool {
|
|
||||||
fieldValue := fl.Field().String()
|
|
||||||
var names []string
|
|
||||||
if err := database.DB.Model(&models.SubscriptionModel{}).Pluck("name", &names).Error; err != nil {
|
|
||||||
logger.Error.Fatalf("Couldn't get SubscriptionModel names: %#v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return slices.Contains(names, fieldValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IBANValidator(fl validator.FieldLevel) bool {
|
|
||||||
fieldValue := fl.Field().String()
|
|
||||||
|
|
||||||
return iban.Validate(fieldValue) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateRequiredMembershipField(fl validator.FieldLevel) bool {
|
|
||||||
user := fl.Top().Interface().(*models.User)
|
|
||||||
membership := user.Membership
|
|
||||||
subModel := membership.SubscriptionModel
|
|
||||||
|
|
||||||
// Get the field name specified in RequiredMembershipField
|
|
||||||
fieldName := subModel.RequiredMembershipField
|
|
||||||
if fieldName == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the value of the field specified by RequiredMembershipField
|
|
||||||
fieldValue := reflect.ValueOf(membership).FieldByName(fieldName)
|
|
||||||
|
|
||||||
// Check if the fieldValue is valid
|
|
||||||
if !fieldValue.IsValid() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the fieldValue is a nil pointer
|
|
||||||
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that the fieldValue is an uint
|
|
||||||
var fieldUint uint
|
|
||||||
if fieldValue.Kind() == reflect.Uint {
|
|
||||||
fieldUint = uint(fieldValue.Uint())
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var membershipIDs []uint
|
|
||||||
if err := database.DB.Model(&models.Membership{}).Pluck("id", &membershipIDs).Error; err != nil {
|
|
||||||
logger.Error.Fatalf("Couldn't get SubscriptionModel names: %#v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the field value is zero (empty)
|
|
||||||
return slices.Contains(membershipIDs, fieldUint)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BICValidator(fl validator.FieldLevel) bool {
|
|
||||||
fieldValue := fl.Field().String()
|
|
||||||
|
|
||||||
return swift.Validate(fieldValue) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateSafeContent(fl validator.FieldLevel) bool {
|
|
||||||
input := strings.ToLower(fl.Field().String())
|
|
||||||
for _, pattern := range xssPatterns {
|
|
||||||
if pattern.MatchString(input) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateDriversLicence(fl validator.FieldLevel) bool {
|
|
||||||
fieldValue := fl.Field().String()
|
|
||||||
if len(fieldValue) != 11 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
id, tenthChar := string(fieldValue[:9]), string(fieldValue[9])
|
|
||||||
|
|
||||||
if tenthChar == "X" {
|
|
||||||
tenthChar = "10"
|
|
||||||
}
|
|
||||||
tenthValue, _ := strconv.ParseInt(tenthChar, 10, 8)
|
|
||||||
|
|
||||||
// for readability
|
|
||||||
weights := []int{9, 8, 7, 6, 5, 4, 3, 2, 1}
|
|
||||||
sum := 0
|
|
||||||
|
|
||||||
for i := 0; i < 9; i++ {
|
|
||||||
char := string(id[i])
|
|
||||||
value, _ := strconv.ParseInt(char, 36, 64)
|
|
||||||
sum += int(value) * weights[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
calcCheckDigit := sum % 11
|
|
||||||
if calcCheckDigit != int(tenthValue) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
38
internal/validation/DriversLicence_validation.go
Normal file
38
internal/validation/DriversLicence_validation.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidateDriversLicence(fl validator.FieldLevel) bool {
|
||||||
|
fieldValue := fl.Field().String()
|
||||||
|
if len(fieldValue) != 11 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
id, tenthChar := string(fieldValue[:9]), string(fieldValue[9])
|
||||||
|
|
||||||
|
if tenthChar == "X" {
|
||||||
|
tenthChar = "10"
|
||||||
|
}
|
||||||
|
tenthValue, _ := strconv.ParseInt(tenthChar, 10, 8)
|
||||||
|
|
||||||
|
// for readability
|
||||||
|
weights := []int{9, 8, 7, 6, 5, 4, 3, 2, 1}
|
||||||
|
sum := 0
|
||||||
|
|
||||||
|
for i := 0; i < 9; i++ {
|
||||||
|
char := string(id[i])
|
||||||
|
value, _ := strconv.ParseInt(char, 36, 64)
|
||||||
|
sum += int(value) * weights[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
calcCheckDigit := sum % 11
|
||||||
|
if calcCheckDigit != int(tenthValue) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
19
internal/validation/bankAccount_validation.go
Normal file
19
internal/validation/bankAccount_validation.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/jbub/banking/iban"
|
||||||
|
"github.com/jbub/banking/swift"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IBANValidator(fl validator.FieldLevel) bool {
|
||||||
|
fieldValue := fl.Field().String()
|
||||||
|
|
||||||
|
return iban.Validate(fieldValue) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BICValidator(fl validator.FieldLevel) bool {
|
||||||
|
fieldValue := fl.Field().String()
|
||||||
|
|
||||||
|
return swift.Validate(fieldValue) == nil
|
||||||
|
}
|
||||||
34
internal/validation/general_validation.go
Normal file
34
internal/validation/general_validation.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
var xssPatterns = []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`(?i)<script`),
|
||||||
|
regexp.MustCompile(`(?i)javascript:`),
|
||||||
|
regexp.MustCompile(`(?i)on\w+\s*=`),
|
||||||
|
regexp.MustCompile(`(?i)(vbscript|data):`),
|
||||||
|
regexp.MustCompile(`(?i)<(iframe|object|embed|applet)`),
|
||||||
|
regexp.MustCompile(`(?i)expression\s*\(`),
|
||||||
|
regexp.MustCompile(`(?i)url\s*\(`),
|
||||||
|
regexp.MustCompile(`(?i)<\?`),
|
||||||
|
regexp.MustCompile(`(?i)<%`),
|
||||||
|
regexp.MustCompile(`(?i)<!\[CDATA\[`),
|
||||||
|
regexp.MustCompile(`(?i)<(svg|animate)`),
|
||||||
|
regexp.MustCompile(`(?i)<(audio|video|source)`),
|
||||||
|
regexp.MustCompile(`(?i)base64`),
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateSafeContent(fl validator.FieldLevel) bool {
|
||||||
|
input := strings.ToLower(fl.Field().String())
|
||||||
|
for _, pattern := range xssPatterns {
|
||||||
|
if pattern.MatchString(input) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
30
internal/validation/membership_validation.go
Normal file
30
internal/validation/membership_validation.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/internal/models"
|
||||||
|
"GoMembership/internal/repositories"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateMembership(sl validator.StructLevel, membership models.Membership) {
|
||||||
|
if membership.SubscriptionModel.RequiredMembershipField != "" {
|
||||||
|
switch membership.SubscriptionModel.RequiredMembershipField {
|
||||||
|
case "ParentMembershipID":
|
||||||
|
if membership.ParentMembershipID == 0 {
|
||||||
|
sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField,
|
||||||
|
"RequiredMembershipField", "required", "")
|
||||||
|
} else {
|
||||||
|
_, err := repositories.GetUserByID(&membership.ParentMembershipID)
|
||||||
|
if err != nil {
|
||||||
|
sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField,
|
||||||
|
"RequiredMembershipField", "user_id_not_found", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField,
|
||||||
|
"RequiredMembershipField", "not_implemented", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
23
internal/validation/setup.go
Normal file
23
internal/validation/setup.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/internal/models"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupValidators() {
|
||||||
|
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||||
|
// Register custom validators
|
||||||
|
v.RegisterValidation("safe_content", ValidateSafeContent)
|
||||||
|
v.RegisterValidation("iban", IBANValidator)
|
||||||
|
v.RegisterValidation("bic", BICValidator)
|
||||||
|
v.RegisterValidation("euDriversLicence", ValidateDriversLicence)
|
||||||
|
|
||||||
|
// Register struct-level validations
|
||||||
|
v.RegisterStructValidation(validateUser, models.User{})
|
||||||
|
v.RegisterStructValidation(ValidateSubscription, models.SubscriptionModel{})
|
||||||
|
}
|
||||||
|
}
|
||||||
46
internal/validation/subscription_validation.go
Normal file
46
internal/validation/subscription_validation.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/internal/models"
|
||||||
|
"GoMembership/internal/repositories"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateNewSubscription validates a new subscription model being created
|
||||||
|
func ValidateSubscription(sl validator.StructLevel) {
|
||||||
|
subscription := sl.Current().Interface().(models.SubscriptionModel)
|
||||||
|
|
||||||
|
if subscription.Name == "" {
|
||||||
|
sl.ReportError(subscription.Name, "Name", "name", "required", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sl.Parent().Type().Name() == "MembershipData" {
|
||||||
|
// This is subscription only operation
|
||||||
|
if subscription.Details == "" {
|
||||||
|
sl.ReportError(subscription.Details, "Details", "details", "required", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subscription.MonthlyFee < 0 {
|
||||||
|
sl.ReportError(subscription.MonthlyFee, "MonthlyFee", "monthly_fee", "gte", "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subscription.HourlyRate < 0 {
|
||||||
|
sl.ReportError(subscription.HourlyRate, "HourlyRate", "hourly_rate", "gte", "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subscription.IncludedPerYear < 0 {
|
||||||
|
sl.ReportError(subscription.IncludedPerYear, "IncludedPerYear", "included_hours_per_year", "gte", "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subscription.IncludedPerMonth < 0 {
|
||||||
|
sl.ReportError(subscription.IncludedPerMonth, "IncludedPerMonth", "included_hours_per_month", "gte", "0")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is a nested probably user struct. We are only checking if the model exists
|
||||||
|
existingSubscription, err := repositories.GetModelByName(&subscription.Name)
|
||||||
|
if err != nil || existingSubscription == nil {
|
||||||
|
sl.ReportError(subscription.Name, "Name", "name", "exists", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
internal/validation/user_validation.go
Normal file
51
internal/validation/user_validation.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/internal/models"
|
||||||
|
"GoMembership/internal/repositories"
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateUser(sl validator.StructLevel) {
|
||||||
|
user := sl.Current().Interface().(models.User)
|
||||||
|
|
||||||
|
if user.DateOfBirth.After(time.Now().AddDate(-18, 0, 0)) {
|
||||||
|
sl.ReportError(user.DateOfBirth, "DateOfBirth", "date_of_birth", "age", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Membership.SubscriptionModel.Name == "" {
|
||||||
|
sl.ReportError(user.Membership.SubscriptionModel.Name, "SubscriptionModel.Name", "name", "required", "")
|
||||||
|
} else {
|
||||||
|
selectedModel, err := repositories.GetModelByName(&user.Membership.SubscriptionModel.Name)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Printf("Error finding subscription model for user %v: %v", user.Email, err)
|
||||||
|
sl.ReportError(user.Membership.SubscriptionModel.Name, "SubscriptionModel.Name", "name", "invalid", "")
|
||||||
|
} else {
|
||||||
|
user.Membership.SubscriptionModel = *selectedModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validateMembership(sl, user.Membership)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func RequiredIfNotAdmin(fl validator.FieldLevel) bool {
|
||||||
|
// // Traverse up the struct hierarchy to find the IsAdmin field
|
||||||
|
// current := fl.Parent()
|
||||||
|
|
||||||
|
// // Check multiple levels of nesting to find userRole
|
||||||
|
// for current.IsValid() {
|
||||||
|
// if isRoleIDField := current.FieldByName("RoleID"); isRoleIDField.IsValid() {
|
||||||
|
// // If IsAdmin is found and is true, skip validation
|
||||||
|
// if isRoleIDField.Interface().(int8) == constants.Roles.Admin{
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// current = current.Parent() // Move to the next parent level
|
||||||
|
// }
|
||||||
|
|
||||||
|
// If not an admin, enforce that the field must have a non-zero value
|
||||||
|
// return !fl.Field().IsZero()
|
||||||
|
// }
|
||||||
Reference in New Issue
Block a user