feat: Update database models and DTOs to use bytea for ULIDWrapper and add JWT configuration to environment
This commit is contained in:
@@ -152,7 +152,11 @@ func (h *CustomerHandler) CreateCustomer(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Convert DTO to model
|
||||
customerCreate := convertCreateCustomerDTOToModel(customerCreateDTO)
|
||||
customerCreate, err := convertCreateCustomerDTOToModel(customerCreateDTO)
|
||||
if err != nil {
|
||||
utils.BadRequestResponse(c, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Create customer in the database
|
||||
customer, err := models.CreateCustomer(c.Request.Context(), customerCreate)
|
||||
@@ -203,7 +207,11 @@ func (h *CustomerHandler) UpdateCustomer(c *gin.Context) {
|
||||
customerUpdateDTO.ID = id.String()
|
||||
|
||||
// Convert DTO to model
|
||||
customerUpdate := convertUpdateCustomerDTOToModel(customerUpdateDTO)
|
||||
customerUpdate, err := convertUpdateCustomerDTOToModel(customerUpdateDTO)
|
||||
if err != nil {
|
||||
utils.BadRequestResponse(c, "Invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Update customer in the database
|
||||
customer, err := models.UpdateCustomer(c.Request.Context(), customerUpdate)
|
||||
@@ -264,21 +272,32 @@ func convertCustomerToDTO(customer *models.Customer) dto.CustomerDto {
|
||||
CreatedAt: customer.CreatedAt,
|
||||
UpdatedAt: customer.UpdatedAt,
|
||||
Name: customer.Name,
|
||||
CompanyID: customer.CompanyID,
|
||||
CompanyID: customer.CompanyID.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func convertCreateCustomerDTOToModel(dto dto.CustomerCreateDto) models.CustomerCreate {
|
||||
return models.CustomerCreate{
|
||||
func convertCreateCustomerDTOToModel(dto dto.CustomerCreateDto) (models.CustomerCreate, error) {
|
||||
|
||||
companyID, err := models.ULIDWrapperFromString(dto.CompanyID)
|
||||
if err != nil {
|
||||
return models.CustomerCreate{}, fmt.Errorf("invalid company ID: %w", err)
|
||||
}
|
||||
|
||||
create := models.CustomerCreate{
|
||||
Name: dto.Name,
|
||||
CompanyID: dto.CompanyID,
|
||||
CompanyID: companyID,
|
||||
}
|
||||
return create, nil
|
||||
}
|
||||
|
||||
func convertUpdateCustomerDTOToModel(dto dto.CustomerUpdateDto) models.CustomerUpdate {
|
||||
id, _ := ulid.Parse(dto.ID)
|
||||
func convertUpdateCustomerDTOToModel(dto dto.CustomerUpdateDto) (models.CustomerUpdate, error) {
|
||||
id, err := models.ULIDWrapperFromString(dto.ID)
|
||||
if err != nil {
|
||||
return models.CustomerUpdate{}, fmt.Errorf("invalid customer ID: %w", err)
|
||||
}
|
||||
|
||||
update := models.CustomerUpdate{
|
||||
ID: models.FromULID(id),
|
||||
ID: id,
|
||||
}
|
||||
|
||||
if dto.Name != nil {
|
||||
@@ -286,10 +305,14 @@ func convertUpdateCustomerDTOToModel(dto dto.CustomerUpdateDto) models.CustomerU
|
||||
}
|
||||
|
||||
if dto.CompanyID != nil {
|
||||
update.CompanyID = dto.CompanyID
|
||||
companyID, err := models.ULIDWrapperFromString(*dto.CompanyID)
|
||||
if err != nil {
|
||||
return models.CustomerUpdate{}, fmt.Errorf("invalid company ID: %w", err)
|
||||
}
|
||||
update.CompanyID = &companyID
|
||||
}
|
||||
|
||||
return update
|
||||
return update, nil
|
||||
}
|
||||
|
||||
// Helper function to parse company ID from string
|
||||
|
||||
@@ -296,36 +296,34 @@ func (h *ProjectHandler) DeleteProject(c *gin.Context) {
|
||||
// Helper functions for DTO conversion
|
||||
|
||||
func convertProjectToDTO(project *models.Project) dto.ProjectDto {
|
||||
customerID := 0
|
||||
if project.CustomerID.Compare(models.ULIDWrapper{}) != 0 {
|
||||
// This is a simplification, adjust as needed
|
||||
customerID = int(project.CustomerID.Time())
|
||||
}
|
||||
|
||||
return dto.ProjectDto{
|
||||
ID: project.ID.String(),
|
||||
CreatedAt: project.CreatedAt,
|
||||
UpdatedAt: project.UpdatedAt,
|
||||
Name: project.Name,
|
||||
CustomerID: customerID,
|
||||
CustomerID: project.CustomerID.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func convertCreateProjectDTOToModel(dto dto.ProjectCreateDto) (models.ProjectCreate, error) {
|
||||
// Convert CustomerID from int to ULID (this is a simplification, adjust as needed)
|
||||
customerID, err := customerIDToULID(dto.CustomerID)
|
||||
customerID, err := models.ULIDWrapperFromString(dto.CustomerID)
|
||||
if err != nil {
|
||||
return models.ProjectCreate{}, fmt.Errorf("invalid customer ID: %w", err)
|
||||
}
|
||||
|
||||
return models.ProjectCreate{
|
||||
Name: dto.Name,
|
||||
CustomerID: models.FromULID(customerID),
|
||||
CustomerID: customerID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convertUpdateProjectDTOToModel(dto dto.ProjectUpdateDto) (models.ProjectUpdate, error) {
|
||||
id, _ := ulid.Parse(dto.ID)
|
||||
id, err := ulid.Parse(dto.ID)
|
||||
if err != nil {
|
||||
return models.ProjectUpdate{}, fmt.Errorf("invalid project ID: %w", err)
|
||||
}
|
||||
update := models.ProjectUpdate{
|
||||
ID: models.FromULID(id),
|
||||
}
|
||||
@@ -336,25 +334,12 @@ func convertUpdateProjectDTOToModel(dto dto.ProjectUpdateDto) (models.ProjectUpd
|
||||
|
||||
if dto.CustomerID != nil {
|
||||
// Convert CustomerID from int to ULID (this is a simplification, adjust as needed)
|
||||
customerID, err := customerIDToULID(*dto.CustomerID)
|
||||
customerID, err := models.ULIDWrapperFromString(*dto.CustomerID)
|
||||
if err != nil {
|
||||
return models.ProjectUpdate{}, fmt.Errorf("invalid customer ID: %w", err)
|
||||
}
|
||||
wrappedID := models.FromULID(customerID)
|
||||
update.CustomerID = &wrappedID
|
||||
update.CustomerID = &customerID
|
||||
}
|
||||
|
||||
return update, nil
|
||||
}
|
||||
|
||||
// Helper function to convert customer ID from int to ULID
|
||||
func customerIDToULID(id int) (ulid.ULID, error) {
|
||||
// This is a simplification, in a real application you would need to
|
||||
// fetch the actual ULID from the database or use a proper conversion method
|
||||
// For now, we'll create a deterministic ULID based on the int value
|
||||
entropy := ulid.Monotonic(nil, 0)
|
||||
timestamp := uint64(id)
|
||||
|
||||
// Create a new ULID with the timestamp and entropy
|
||||
return ulid.MustNew(timestamp, entropy), nil
|
||||
}
|
||||
|
||||
@@ -406,9 +406,9 @@ func convertTimeEntryToDTO(timeEntry *models.TimeEntry) dto.TimeEntryDto {
|
||||
ID: timeEntry.ID.String(),
|
||||
CreatedAt: timeEntry.CreatedAt,
|
||||
UpdatedAt: timeEntry.UpdatedAt,
|
||||
UserID: int(timeEntry.UserID.Time()), // Simplified conversion
|
||||
ProjectID: int(timeEntry.ProjectID.Time()), // Simplified conversion
|
||||
ActivityID: int(timeEntry.ActivityID.Time()), // Simplified conversion
|
||||
UserID: timeEntry.UserID.String(), // Simplified conversion
|
||||
ProjectID: timeEntry.ProjectID.String(), // Simplified conversion
|
||||
ActivityID: timeEntry.ActivityID.String(), // Simplified conversion
|
||||
Start: timeEntry.Start,
|
||||
End: timeEntry.End,
|
||||
Description: timeEntry.Description,
|
||||
@@ -418,25 +418,25 @@ func convertTimeEntryToDTO(timeEntry *models.TimeEntry) dto.TimeEntryDto {
|
||||
|
||||
func convertCreateTimeEntryDTOToModel(dto dto.TimeEntryCreateDto) (models.TimeEntryCreate, error) {
|
||||
// Convert IDs from int to ULID (this is a simplification, adjust as needed)
|
||||
userID, err := idToULID(dto.UserID)
|
||||
userID, err := models.ULIDWrapperFromString(dto.UserID)
|
||||
if err != nil {
|
||||
return models.TimeEntryCreate{}, fmt.Errorf("invalid user ID: %w", err)
|
||||
}
|
||||
|
||||
projectID, err := idToULID(dto.ProjectID)
|
||||
projectID, err := models.ULIDWrapperFromString(dto.ProjectID)
|
||||
if err != nil {
|
||||
return models.TimeEntryCreate{}, fmt.Errorf("invalid project ID: %w", err)
|
||||
}
|
||||
|
||||
activityID, err := idToULID(dto.ActivityID)
|
||||
activityID, err := models.ULIDWrapperFromString(dto.ActivityID)
|
||||
if err != nil {
|
||||
return models.TimeEntryCreate{}, fmt.Errorf("invalid activity ID: %w", err)
|
||||
}
|
||||
|
||||
return models.TimeEntryCreate{
|
||||
UserID: models.FromULID(userID),
|
||||
ProjectID: models.FromULID(projectID),
|
||||
ActivityID: models.FromULID(activityID),
|
||||
UserID: userID,
|
||||
ProjectID: projectID,
|
||||
ActivityID: activityID,
|
||||
Start: dto.Start,
|
||||
End: dto.End,
|
||||
Description: dto.Description,
|
||||
@@ -445,36 +445,36 @@ func convertCreateTimeEntryDTOToModel(dto dto.TimeEntryCreateDto) (models.TimeEn
|
||||
}
|
||||
|
||||
func convertUpdateTimeEntryDTOToModel(dto dto.TimeEntryUpdateDto) (models.TimeEntryUpdate, error) {
|
||||
id, _ := ulid.Parse(dto.ID)
|
||||
id, err := ulid.Parse(dto.ID)
|
||||
if err != nil {
|
||||
return models.TimeEntryUpdate{}, fmt.Errorf("invalid time entry ID: %w", err)
|
||||
}
|
||||
update := models.TimeEntryUpdate{
|
||||
ID: models.FromULID(id),
|
||||
}
|
||||
|
||||
if dto.UserID != nil {
|
||||
userID, err := idToULID(*dto.UserID)
|
||||
userID, err := models.ULIDWrapperFromString(*dto.UserID)
|
||||
if err != nil {
|
||||
return models.TimeEntryUpdate{}, fmt.Errorf("invalid user ID: %w", err)
|
||||
}
|
||||
wrappedID := models.FromULID(userID)
|
||||
update.UserID = &wrappedID
|
||||
update.UserID = &userID
|
||||
}
|
||||
|
||||
if dto.ProjectID != nil {
|
||||
projectID, err := idToULID(*dto.ProjectID)
|
||||
projectID, err := models.ULIDWrapperFromString(*dto.ProjectID)
|
||||
if err != nil {
|
||||
return models.TimeEntryUpdate{}, fmt.Errorf("invalid project ID: %w", err)
|
||||
}
|
||||
wrappedProjectID := models.FromULID(projectID)
|
||||
update.ProjectID = &wrappedProjectID
|
||||
update.ProjectID = &projectID
|
||||
}
|
||||
|
||||
if dto.ActivityID != nil {
|
||||
activityID, err := idToULID(*dto.ActivityID)
|
||||
activityID, err := models.ULIDWrapperFromString(*dto.ActivityID)
|
||||
if err != nil {
|
||||
return models.TimeEntryUpdate{}, fmt.Errorf("invalid activity ID: %w", err)
|
||||
}
|
||||
wrappedActivityID := models.FromULID(activityID)
|
||||
update.ActivityID = &wrappedActivityID
|
||||
update.ActivityID = &activityID
|
||||
}
|
||||
|
||||
if dto.Start != nil {
|
||||
@@ -495,13 +495,3 @@ func convertUpdateTimeEntryDTOToModel(dto dto.TimeEntryUpdateDto) (models.TimeEn
|
||||
|
||||
return update, nil
|
||||
}
|
||||
|
||||
// Helper function to convert ID from int to ULID
|
||||
func idToULID(id int) (ulid.ULID, error) {
|
||||
// This is a simplification, in a real application you would need to
|
||||
// fetch the actual ULID from the database or use a proper conversion method
|
||||
// For now, we'll create a deterministic ULID based on the int value
|
||||
entropy := ulid.Monotonic(nil, 0)
|
||||
timestamp := uint64(id)
|
||||
return ulid.MustNew(timestamp, entropy), nil
|
||||
}
|
||||
|
||||
+78
-4
@@ -1,23 +1,97 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"github.com/timetracker/backend/internal/api/utils"
|
||||
"github.com/timetracker/backend/internal/models"
|
||||
)
|
||||
|
||||
// JWT configuration
|
||||
const (
|
||||
// This should be moved to environment variables in production
|
||||
jwtSecret = "your-secret-key-change-in-production"
|
||||
var (
|
||||
jwtSecret string
|
||||
tokenDuration = 24 * time.Hour
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Load .env file
|
||||
_ = godotenv.Load()
|
||||
|
||||
// Get JWT secret from environment
|
||||
jwtSecret = os.Getenv("JWT_SECRET")
|
||||
|
||||
// Generate a random secret if none is provided
|
||||
if jwtSecret == "" {
|
||||
randomBytes := make([]byte, 32)
|
||||
_, err := rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
panic("failed to generate JWT secret: " + err.Error())
|
||||
}
|
||||
jwtSecret = string(randomBytes)
|
||||
}
|
||||
|
||||
// Generate and store RSA keys if configured
|
||||
if os.Getenv("JWT_KEY_GENERATE") == "true" {
|
||||
keyDir := os.Getenv("JWT_KEY_DIR")
|
||||
if keyDir == "" {
|
||||
keyDir = "./keys"
|
||||
}
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if err := os.MkdirAll(keyDir, 0755); err != nil {
|
||||
panic("failed to create key directory: " + err.Error())
|
||||
}
|
||||
|
||||
// Generate RSA key pair
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
panic("failed to generate RSA key pair: " + err.Error())
|
||||
}
|
||||
|
||||
// Save private key
|
||||
privateKeyFile, err := os.Create(fmt.Sprintf("%s/private.pem", keyDir))
|
||||
if err != nil {
|
||||
panic("failed to create private key file: " + err.Error())
|
||||
}
|
||||
defer privateKeyFile.Close()
|
||||
|
||||
privateKeyPEM := &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
}
|
||||
|
||||
if err := pem.Encode(privateKeyFile, privateKeyPEM); err != nil {
|
||||
panic("failed to encode private key: " + err.Error())
|
||||
}
|
||||
|
||||
// Save public key
|
||||
publicKeyFile, err := os.Create(fmt.Sprintf("%s/public.pem", keyDir))
|
||||
if err != nil {
|
||||
panic("failed to create public key file: " + err.Error())
|
||||
}
|
||||
defer publicKeyFile.Close()
|
||||
|
||||
publicKeyPEM := &pem.Block{
|
||||
Type: "RSA PUBLIC KEY",
|
||||
Bytes: x509.MarshalPKCS1PublicKey(&privateKey.PublicKey),
|
||||
}
|
||||
|
||||
if err := pem.Encode(publicKeyFile, publicKeyPEM); err != nil {
|
||||
panic("failed to encode public key: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Claims represents the JWT claims
|
||||
type Claims struct {
|
||||
UserID string `json:"userId"`
|
||||
Reference in New Issue
Block a user