chore: add user schema

This commit is contained in:
2025-06-05 02:25:09 +01:00
parent 0879aec750
commit b70bdba0dc
9 changed files with 570 additions and 70 deletions
+5 -1
View File
@@ -13,12 +13,16 @@ type API struct {
}
func (api *API) Initialize() {
validator := validator.New(validator.WithRequiredStructEnabled())
validator.RegisterValidation("password", PasswordValidator)
db := &models.DB{}
ech := echo.New()
ech.Validator = &CustomValidator{validator: validator.New(validator.WithRequiredStructEnabled())}
ech.Validator = &CustomValidator{validator: validator}
db.Connect()
db.MigrateClients()
db.MigrateUsers()
api.instance = ech
api.db = db
+9 -9
View File
@@ -6,12 +6,12 @@ import (
"net/http"
"strconv"
"github.com/hazemKrimi/crimson-vault/internal/models"
"github.com/hazemKrimi/crimson-vault/internal/types"
"github.com/labstack/echo/v4"
)
func (api *API) CreateClientHandler(context echo.Context) error {
var body models.CreateClientRequestBody
var body types.CreateClientRequestBody
if err := context.Bind(&body); err != nil {
log.Println(fmt.Sprintf("Error creating Client: %v.", err))
@@ -52,13 +52,13 @@ func (api *API) GetClientHandler(context echo.Context) error {
return context.String(http.StatusInternalServerError, "Unexpected error getting Client!")
}
var client models.Client
var client types.Client
if err := api.db.GetClient(id, &client); err != nil {
return context.String(http.StatusNotFound, "Client not found!")
}
log.Println(fmt.Sprintf("Got client with ID %d.", client.ID))
log.Println(fmt.Sprintf("Got User with ID %d.", client.ID))
return context.JSON(http.StatusOK, client)
}
@@ -75,7 +75,7 @@ func (api *API) UpdateClientHandler(context echo.Context) error {
return context.String(http.StatusInternalServerError, "Unexpected error updating Client!")
}
var body models.UpdateClientRequestBody
var body types.UpdateClientRequestBody
if err := context.Bind(&body); err != nil {
log.Println(fmt.Sprintf("Error updating Client: %v.", err))
@@ -86,13 +86,13 @@ func (api *API) UpdateClientHandler(context echo.Context) error {
return err
}
var client models.Client
var client types.Client
if err := api.db.UpdateClient(id, body, &client); err != nil {
return context.String(http.StatusNotFound, "Client not found!")
}
log.Println(fmt.Sprintf("Updated client with ID %d.", client.ID))
log.Println(fmt.Sprintf("Updated Client with ID %d.", client.ID))
return context.JSON(http.StatusOK, client)
}
@@ -109,12 +109,12 @@ func (api *API) DeleteClientHandler(context echo.Context) error {
return context.String(http.StatusInternalServerError, "Unexpected error deleting Client!")
}
var client models.Client
var client types.Client
if err := api.db.DeleteClient(id); err != nil {
return context.String(http.StatusNotFound, "Client not found!")
}
log.Println(fmt.Sprintf("Deleted client with ID %d.", client.ID))
log.Println(fmt.Sprintf("Deleted Client with ID %d.", client.ID))
return context.String(http.StatusOK, "Client deleted successfully!")
}
+19 -7
View File
@@ -1,11 +1,23 @@
package api
func (api *API) ClientRoutes() {
group := api.instance.Group("/clients")
import "github.com/labstack/echo/v4/middleware"
group.GET("/", api.GetAllClientsHandler)
group.POST("/", api.CreateClientHandler)
group.GET("/:id", api.GetClientHandler)
group.PUT("/:id", api.UpdateClientHandler)
group.DELETE("/:id", api.DeleteClientHandler)
func (api *API) ClientRoutes() {
clients := api.instance.Group("/clients")
users := api.instance.Group("/users")
clients.GET("/", api.GetAllClientsHandler)
clients.POST("/", api.CreateClientHandler)
clients.GET("/:id", api.GetClientHandler)
clients.PUT("/:id", api.UpdateClientHandler)
clients.DELETE("/:id", api.DeleteClientHandler)
users.GET("/", api.GetAllUsersHandler)
users.POST("/", api.CreateUserHandler)
users.GET("/:id", api.GetUserHandler)
users.PUT("/:id", api.UpdateUserHandler)
users.PUT("/:id/security", api.UpdateUserSecurityDetailsHandler)
users.PUT("/:id/logo", api.UpdateUserLogoHandler, middleware.BodyLimit("2M"))
users.DELETE("/:id", api.DeleteUserHandler)
users.DELETE("/:id/logo", api.DeleteUserLogoHandler)
}
+279
View File
@@ -0,0 +1,279 @@
package api
import (
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/hazemKrimi/crimson-vault/internal/types"
"github.com/labstack/echo/v4"
)
func (api *API) CreateUserHandler(context echo.Context) error {
var body types.CreateUserRequestBody
if err := context.Bind(&body); err != nil {
log.Println(fmt.Sprintf("Error creating User: %v.", err))
return context.String(http.StatusBadRequest, "Invalid JSON!")
}
if err := context.Validate(body); err != nil {
return err
}
user := api.db.CreateUser(body)
log.Println(fmt.Sprintf("User created with ID %d.", user.ID))
return context.JSON(http.StatusOK, user)
}
func (api *API) GetAllUsersHandler(context echo.Context) error {
users, err := api.db.GetUsers()
if err != nil {
return context.String(http.StatusInternalServerError, "Unexpected error getting User!")
}
log.Println("Got all Users.")
return context.JSON(http.StatusOK, users)
}
func (api *API) GetUserHandler(context echo.Context) error {
idString := context.Param("id")
if idString == "" {
return context.String(http.StatusBadRequest, "ID is required to get a User!")
}
id, err := strconv.Atoi(idString)
if err != nil {
return context.String(http.StatusInternalServerError, "Unexpected error getting User!")
}
var user types.User
if err := api.db.GetUser(id, &user); err != nil {
return context.String(http.StatusNotFound, "User not found!")
}
log.Println(fmt.Sprintf("Got User with ID %d.", user.ID))
return context.JSON(http.StatusOK, user)
}
func (api *API) UpdateUserHandler(context echo.Context) error {
idString := context.Param("id")
if idString == "" {
return context.String(http.StatusBadRequest, "ID is required to update a User!")
}
id, err := strconv.Atoi(idString)
if err != nil {
return context.String(http.StatusInternalServerError, "Unexpected error updating User!")
}
var body types.UpdateUserRequestBody
if err := context.Bind(&body); err != nil {
log.Println(fmt.Sprintf("Error updating User: %v.", err))
return context.String(http.StatusBadRequest, "Invalid JSON!")
}
if err := context.Validate(body); err != nil {
return err
}
var user types.User
if err := api.db.UpdateUser(id, body, &user); err != nil {
return context.String(http.StatusNotFound, "User not found!")
}
log.Println(fmt.Sprintf("Updated user with ID %d.", user.ID))
return context.JSON(http.StatusOK, user)
}
func (api *API) UpdateUserSecurityDetailsHandler(context echo.Context) error {
idString := context.Param("id")
if idString == "" {
return context.String(http.StatusBadRequest, "ID is required to create security details for a User!")
}
id, err := strconv.Atoi(idString)
if err != nil {
return context.String(http.StatusInternalServerError, "Unexpected error while creating security details for User!")
}
var body types.UpdateUserSecurityDetailsBody
if err := context.Bind(&body); err != nil {
log.Println(fmt.Sprintf("Error creating security details for User: %v.", err))
return context.String(http.StatusBadRequest, "Invalid JSON!")
}
if err := context.Validate(body); err != nil {
return err
}
var user types.User
if err := api.db.UpdateUserSecurityDetails(id, body, &user); err != nil {
return context.String(http.StatusNotFound, "User not found!")
}
log.Println(fmt.Sprintf("Updated security details of user with ID %d.", user.ID))
return context.JSON(http.StatusOK, user)
}
func (api *API) UpdateUserLogoHandler(context echo.Context) error {
idString := context.Param("id")
if idString == "" {
return context.String(http.StatusBadRequest, "ID is required to update logo for User!")
}
id, err := strconv.Atoi(idString)
if err != nil {
return context.String(http.StatusInternalServerError, "Unexpected error updating logo for User!")
}
var user types.User
if err := api.db.GetUser(id, &user); err != nil {
return context.String(http.StatusNotFound, "User not found!")
}
if user.Username == "" {
return context.String(http.StatusBadRequest, "You have to add a username first for this User!")
}
file, err := context.FormFile("logo")
if err != nil {
log.Println(fmt.Sprintf("Error updating logo for User: %v.", err))
return context.String(http.StatusInternalServerError, "Unexpected error while updating logo for User!")
}
ext := strings.ToLower(filepath.Ext(file.Filename))
allowedExtensions := map[string]bool{
".jpg": true,
".jpeg": true,
".png": true,
".gif": true,
".bmp": true,
".webp": true,
}
if !allowedExtensions[ext] {
return context.String(http.StatusBadRequest, "Invalid file type, only image files are allowed!")
}
src, err := file.Open()
if err != nil {
log.Println(fmt.Sprintf("Error updating logo for User: %v.", err))
return context.String(http.StatusInternalServerError, "Unexpected error while updating logo for User!")
}
defer src.Close()
data, err := io.ReadAll(src)
if err != nil {
return context.String(http.StatusInternalServerError, "Unexpected error while updating logo for User!")
}
filetype := http.DetectContentType(data)
if !strings.HasPrefix(filetype, "image/") {
return context.String(http.StatusBadRequest, "Uploaded file is not a valid image!")
}
err = os.MkdirAll(user.Username, os.ModePerm)
if err != nil {
log.Println(fmt.Sprintf("Error updating logo for User: %v.", err))
return context.String(http.StatusInternalServerError, "Unexpected error while updating logo for User!")
}
path, err := filepath.Abs(filepath.Join(user.Username, fmt.Sprintf("logo%s", ext)))
if err != nil {
log.Println(fmt.Sprintf("Error updating logo for User: %v.", err))
return context.String(http.StatusInternalServerError, "Unexpected error while updating logo for User!")
}
if err := os.WriteFile(path, data, 0644); err != nil {
log.Println(fmt.Sprintf("Error updating logo for User: %v.", err))
return context.String(http.StatusInternalServerError, "Unexpected error while updating logo for User!")
}
if err := api.db.UpdateUserLogo(path, &user); err != nil {
log.Println(fmt.Sprintf("Error updating logo for User: %v.", err))
return context.String(http.StatusInternalServerError, "Unexpected error while updating logo for User!")
}
return context.JSON(http.StatusOK, user)
}
func (api *API) DeleteUserHandler(context echo.Context) error {
idString := context.Param("id")
if idString == "" {
return context.String(http.StatusBadRequest, "ID is required to delete a User!")
}
id, err := strconv.Atoi(idString)
if err != nil {
return context.String(http.StatusInternalServerError, "Unexpected error deleting User!")
}
if err := api.db.DeleteUser(id); err != nil {
return context.String(http.StatusNotFound, "User not found!")
}
log.Println(fmt.Sprintf("Deleted User with ID %d.", id))
return context.String(http.StatusOK, "User deleted successfully!")
}
func (api *API) DeleteUserLogoHandler(context echo.Context) error {
idString := context.Param("id")
if idString == "" {
return context.String(http.StatusBadRequest, "ID is required to delete logo of User!")
}
id, err := strconv.Atoi(idString)
if err != nil {
log.Println(fmt.Sprintf("Error deleting logo of User: %v.", err))
return context.String(http.StatusInternalServerError, "Unexpected error deleting logo of User!")
}
var user types.User
if err := api.db.GetUser(id, &user); err != nil {
return context.String(http.StatusNotFound, "User not found!")
}
os.Remove(user.Logo)
if err := api.db.DeleteUserLogo(&user); err != nil {
log.Println(fmt.Sprintf("Error deleting logo of User: %v.", err))
return context.String(http.StatusInternalServerError, "Unexpected error deleting logo of User!")
}
log.Println(fmt.Sprintf("Deleted logo of User with ID %d.", user.ID))
return context.String(http.StatusOK, "User logo deleted successfully!")
}
+19
View File
@@ -2,6 +2,7 @@ package api
import (
"net/http"
"regexp"
"github.com/go-playground/validator/v10"
"github.com/labstack/echo/v4"
@@ -17,3 +18,21 @@ func (validator *CustomValidator) Validate(i any) error {
}
return nil
}
func PasswordValidator(fieldLevel validator.FieldLevel) bool {
password := fieldLevel.Field().String()
var (
upper = regexp.MustCompile(`[A-Z]`)
lower = regexp.MustCompile(`[a-z]`)
number = regexp.MustCompile(`[0-9]`)
special = regexp.MustCompile(`[!@#~$%^&*()+|_{}:<>?,./;'\[\]\\-]`)
minChars = 8
)
return len(password) >= minChars &&
upper.MatchString(password) &&
lower.MatchString(password) &&
number.MatchString(password) &&
special.MatchString(password)
}
+26 -54
View File
@@ -1,58 +1,30 @@
package models
import (
"time"
"gorm.io/gorm"
"github.com/hazemKrimi/crimson-vault/internal/types"
)
type Client struct {
ID uint32 `json:"id"`
CreatedAt time.Time `json:"createAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `json:"deletedAt" gorm:"index"`
Name string `json:"name"`
FiscalCode string `json:"fiscalCode"`
Address string `json:"address"`
Zip string `json:"zip"`
Country string `json:"country"`
Phone string `json:"phone"`
Email string `json:"email"`
}
type CreateClientRequestBody struct {
Name string `json:"name" validate:"required"`
FiscalCode string `json:"fiscalCode"`
Address string `json:"address" validate:"required"`
Zip string `json:"zip" validate:"required"`
Country string `json:"country" validate:"required"`
Phone string `json:"phone" validate:"required,e164"`
Email string `json:"email" validate:"required,email"`
}
type UpdateClientRequestBody struct {
Name string `json:"name"`
FiscalCode string `json:"fiscalCode"`
Address string `json:"address"`
Zip string `json:"zip"`
Country string `json:"country"`
Phone string `json:"phone" validate:"omitempty,e164"`
Email string `json:"email" validate:"omitempty,email"`
}
func (db *DB) MigrateClients() {
db.instance.AutoMigrate(&Client{})
db.instance.AutoMigrate(&types.Client{})
}
func (db *DB) CreateClient(body CreateClientRequestBody) Client {
client := Client{Name: body.Name, Country: body.Country, Phone: body.Phone}
func (db *DB) CreateClient(body types.CreateClientRequestBody) types.Client {
client := types.Client{
Name: body.Name,
FiscalCode: body.FiscalCode,
Address: body.Address,
Zip: body.Zip,
Country: body.Country,
Phone: body.Phone,
Email: body.Email,
}
db.instance.Create(&client)
return client
}
func (db *DB) GetClients() ([]Client, error) {
var clients []Client
func (db *DB) GetClients() ([]types.Client, error) {
var clients []types.Client
result := db.instance.Find(&clients)
@@ -63,8 +35,8 @@ func (db *DB) GetClients() ([]Client, error) {
return clients, nil
}
func (db *DB) GetClient(id int, client *Client) error {
result := db.instance.Where("id = ?", id).First(&client, id)
func (db *DB) GetClient(id int, client *types.Client) error {
result := db.instance.Where("id = ?", id).First(client, id)
if result.Error != nil {
return result.Error
@@ -73,21 +45,21 @@ func (db *DB) GetClient(id int, client *Client) error {
return nil
}
func (db *DB) UpdateClient(id int, body UpdateClientRequestBody, client *Client) error {
result := db.instance.Where("id = ?", id).First(&client, id)
func (db *DB) UpdateClient(id int, body types.UpdateClientRequestBody, client *types.Client) error {
result := db.instance.Where("id = ?", id).First(client, id)
if result.Error != nil {
return result.Error
}
result = db.instance.Model(&client).Updates(Client{
Name: body.Name,
result = db.instance.Model(client).Updates(types.Client{
Name: body.Name,
FiscalCode: body.FiscalCode,
Address: body.Address,
Zip: body.Zip,
Country: body.Country,
Phone: body.Phone,
Email: body.Email,
Address: body.Address,
Zip: body.Zip,
Country: body.Country,
Phone: body.Phone,
Email: body.Email,
})
if result.Error != nil {
@@ -98,7 +70,7 @@ func (db *DB) UpdateClient(id int, body UpdateClientRequestBody, client *Client)
}
func (db *DB) DeleteClient(id int) error {
result := db.instance.Delete(&Client{}, id)
result := db.instance.Delete(&types.Client{}, id)
if result.Error != nil {
return result.Error
+123
View File
@@ -0,0 +1,123 @@
package models
import (
"github.com/hazemKrimi/crimson-vault/internal/types"
)
func (db *DB) MigrateUsers() {
db.instance.AutoMigrate(&types.User{})
}
func (db *DB) CreateUser(body types.CreateUserRequestBody) types.User {
user := types.User{
Name: body.Name,
FiscalCode: body.FiscalCode,
Address: body.Address,
Zip: body.Zip,
Country: body.Country,
Phone: body.Phone,
Email: body.Email,
}
db.instance.Create(&user)
return user
}
func (db *DB) GetUsers() ([]types.User, error) {
var users []types.User
result := db.instance.Find(&users)
if result.Error != nil {
return nil, result.Error
}
return users, nil
}
func (db *DB) GetUser(id int, user *types.User) error {
result := db.instance.Where("id = ?", id).First(user, id)
if result.Error != nil {
return result.Error
}
return nil
}
func (db *DB) UpdateUser(id int, body types.UpdateUserRequestBody, user *types.User) error {
result := db.instance.Where("id = ?", id).First(user, id)
if result.Error != nil {
return result.Error
}
result = db.instance.Model(user).Updates(types.User{
Name: body.Name,
FiscalCode: body.FiscalCode,
Address: body.Address,
Zip: body.Zip,
Country: body.Country,
Phone: body.Phone,
Email: body.Email,
})
if result.Error != nil {
return result.Error
}
return nil
}
func (db *DB) UpdateUserSecurityDetails(id int, body types.UpdateUserSecurityDetailsBody, user *types.User) error {
result := db.instance.Where("id = ?", id).First(user, id)
if result.Error != nil {
return result.Error
}
result = db.instance.Model(user).Updates(types.User{
Username: body.Username,
Password: body.Password,
})
if result.Error != nil {
return result.Error
}
return nil
}
func (db *DB) UpdateUserLogo(path string, user *types.User) error {
result := db.instance.Model(user).Updates(types.User{
Logo: path,
})
if result.Error != nil {
return result.Error
}
return nil
}
func (db *DB) DeleteUser(id int) error {
result := db.instance.Delete(&types.User{}, id)
if result.Error != nil {
return result.Error
}
return nil
}
func (db *DB) DeleteUserLogo(user *types.User) error {
result := db.instance.Model(user).Updates(&types.User{
Logo: "",
})
if result.Error != nil {
return result.Error
}
return nil
}
+41
View File
@@ -0,0 +1,41 @@
package types
import (
"time"
"gorm.io/gorm"
)
type Client struct {
ID uint32 `json:"id"`
CreatedAt time.Time `json:"createAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `json:"deletedAt" gorm:"index"`
Name string `json:"name"`
FiscalCode string `json:"fiscalCode"`
Address string `json:"address"`
Zip string `json:"zip"`
Country string `json:"country"`
Phone string `json:"phone"`
Email string `json:"email"`
}
type CreateClientRequestBody struct {
Name string `json:"name" validate:"required"`
FiscalCode string `json:"fiscalCode"`
Address string `json:"address" validate:"required"`
Zip string `json:"zip" validate:"required"`
Country string `json:"country" validate:"required"`
Phone string `json:"phone" validate:"required,e164"`
Email string `json:"email" validate:"required,email"`
}
type UpdateClientRequestBody struct {
Name string `json:"name"`
FiscalCode string `json:"fiscalCode"`
Address string `json:"address"`
Zip string `json:"zip"`
Country string `json:"country"`
Phone string `json:"phone" validate:"omitempty,e164"`
Email string `json:"email" validate:"omitempty,email"`
}
+50
View File
@@ -0,0 +1,50 @@
package types
import (
"time"
"gorm.io/gorm"
)
type User struct {
ID uint32 `json:"id"`
CreatedAt time.Time `json:"createAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `json:"deletedAt" gorm:"index"`
Logo string `json:"logo"`
Name string `json:"name"`
FiscalCode string `json:"fiscalCode"`
Address string `json:"address"`
Zip string `json:"zip"`
Country string `json:"country"`
Phone string `json:"phone"`
Email string `json:"email"`
Username string `json:"username"`
Password string `json:"password"`
}
type CreateUserRequestBody struct {
Name string `json:"name" validate:"required"`
FiscalCode string `json:"fiscalCode" validate:"required"`
Address string `json:"address" validate:"required"`
Zip string `json:"zip" validate:"required"`
Country string `json:"country" validate:"required"`
Phone string `json:"phone" validate:"required,e164"`
Email string `json:"email" validate:"required,email"`
}
type UpdateUserRequestBody struct {
Name string `json:"name"`
FiscalCode string `json:"fiscalCode"`
Address string `json:"address"`
Zip string `json:"zip"`
Country string `json:"country"`
Phone string `json:"phone" validate:"omitempty,e164"`
Email string `json:"email" validate:"omitempty,email"`
}
type UpdateUserSecurityDetailsBody struct {
Username string `json:"username"`
Password string `json:"password" validate:"password"`
ConfirmPassword string `json:"confirmPassword" validate:"password,eqcsfield=Password"`
}