Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
119
cloud/maplepress-backend/internal/repository/user/create.go
Normal file
119
cloud/maplepress-backend/internal/repository/user/create.go
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"go.uber.org/zap"
|
||||
|
||||
domainuser "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/repository/user/models"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/logger"
|
||||
)
|
||||
|
||||
// Create creates a new user in all tables using batched writes
|
||||
func (r *repository) Create(ctx context.Context, tenantID string, u *domainuser.User) error {
|
||||
// CWE-532: Use redacted email for logging
|
||||
r.logger.Info("creating user",
|
||||
zap.String("tenant_id", tenantID),
|
||||
logger.EmailHash(u.Email),
|
||||
logger.SafeEmail("email_redacted", u.Email))
|
||||
|
||||
// Convert domain entity to ALL table models
|
||||
userByID := models.FromUser(tenantID, u)
|
||||
userByEmail := models.FromUserByEmail(tenantID, u)
|
||||
userByDate := models.FromUserByDate(tenantID, u)
|
||||
|
||||
// Use batched writes to maintain consistency across all tables
|
||||
batch := r.session.NewBatch(gocql.LoggedBatch)
|
||||
|
||||
// Insert into users_by_id table
|
||||
batch.Query(`INSERT INTO users_by_id (tenant_id, id, email, first_name, last_name, name, lexical_name, timezone, role, status,
|
||||
phone, country, region, city, postal_code, address_line1, address_line2,
|
||||
has_shipping_address, shipping_name, shipping_phone, shipping_country, shipping_region,
|
||||
shipping_city, shipping_postal_code, shipping_address_line1, shipping_address_line2, profile_timezone,
|
||||
agree_terms_of_service, agree_promotions, agree_to_tracking_across_third_party_apps_and_services,
|
||||
password_hash_algorithm, password_hash, was_email_verified, code, code_type, code_expiry,
|
||||
otp_enabled, otp_verified, otp_validated, otp_secret, otp_auth_url, otp_backup_code_hash, otp_backup_code_hash_algorithm,
|
||||
created_from_ip_address, created_from_ip_timestamp, created_by_user_id, created_by_name,
|
||||
modified_from_ip_address, modified_from_ip_timestamp, modified_by_user_id, modified_at, modified_by_name, last_login_at,
|
||||
created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
userByID.TenantID, userByID.ID, userByID.Email, userByID.FirstName, userByID.LastName, userByID.Name,
|
||||
userByID.LexicalName, userByID.Timezone, userByID.Role, userByID.Status,
|
||||
userByID.Phone, userByID.Country, userByID.Region, userByID.City, userByID.PostalCode,
|
||||
userByID.AddressLine1, userByID.AddressLine2, userByID.HasShippingAddress, userByID.ShippingName,
|
||||
userByID.ShippingPhone, userByID.ShippingCountry, userByID.ShippingRegion, userByID.ShippingCity,
|
||||
userByID.ShippingPostalCode, userByID.ShippingAddressLine1, userByID.ShippingAddressLine2, userByID.ProfileTimezone,
|
||||
userByID.AgreeTermsOfService, userByID.AgreePromotions, userByID.AgreeToTrackingAcrossThirdPartyAppsAndServices,
|
||||
userByID.PasswordHashAlgorithm, userByID.PasswordHash, userByID.WasEmailVerified,
|
||||
userByID.Code, userByID.CodeType, userByID.CodeExpiry,
|
||||
userByID.OTPEnabled, userByID.OTPVerified, userByID.OTPValidated, userByID.OTPSecret,
|
||||
userByID.OTPAuthURL, userByID.OTPBackupCodeHash, userByID.OTPBackupCodeHashAlgorithm,
|
||||
userByID.CreatedFromIPAddress, userByID.CreatedFromIPTimestamp, userByID.CreatedByUserID, userByID.CreatedByName,
|
||||
userByID.ModifiedFromIPAddress, userByID.ModifiedFromIPTimestamp, userByID.ModifiedByUserID, userByID.ModifiedAt, userByID.ModifiedByName,
|
||||
userByID.LastLoginAt, userByID.CreatedAt, userByID.UpdatedAt)
|
||||
|
||||
// Insert into users_by_email table
|
||||
batch.Query(`INSERT INTO users_by_email (tenant_id, email, id, first_name, last_name, name, lexical_name, timezone, role, status,
|
||||
phone, country, region, city, postal_code, address_line1, address_line2,
|
||||
has_shipping_address, shipping_name, shipping_phone, shipping_country, shipping_region,
|
||||
shipping_city, shipping_postal_code, shipping_address_line1, shipping_address_line2, profile_timezone,
|
||||
agree_terms_of_service, agree_promotions, agree_to_tracking_across_third_party_apps_and_services,
|
||||
password_hash_algorithm, password_hash, was_email_verified, code, code_type, code_expiry,
|
||||
otp_enabled, otp_verified, otp_validated, otp_secret, otp_auth_url, otp_backup_code_hash, otp_backup_code_hash_algorithm,
|
||||
created_from_ip_address, created_from_ip_timestamp, created_by_user_id, created_by_name,
|
||||
modified_from_ip_address, modified_from_ip_timestamp, modified_by_user_id, modified_at, modified_by_name, last_login_at,
|
||||
created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
userByEmail.TenantID, userByEmail.Email, userByEmail.ID, userByEmail.FirstName, userByEmail.LastName, userByEmail.Name,
|
||||
userByEmail.LexicalName, userByEmail.Timezone, userByEmail.Role, userByEmail.Status,
|
||||
userByEmail.Phone, userByEmail.Country, userByEmail.Region, userByEmail.City, userByEmail.PostalCode,
|
||||
userByEmail.AddressLine1, userByEmail.AddressLine2, userByEmail.HasShippingAddress, userByEmail.ShippingName,
|
||||
userByEmail.ShippingPhone, userByEmail.ShippingCountry, userByEmail.ShippingRegion, userByEmail.ShippingCity,
|
||||
userByEmail.ShippingPostalCode, userByEmail.ShippingAddressLine1, userByEmail.ShippingAddressLine2, userByEmail.ProfileTimezone,
|
||||
userByEmail.AgreeTermsOfService, userByEmail.AgreePromotions, userByEmail.AgreeToTrackingAcrossThirdPartyAppsAndServices,
|
||||
userByEmail.PasswordHashAlgorithm, userByEmail.PasswordHash, userByEmail.WasEmailVerified,
|
||||
userByEmail.Code, userByEmail.CodeType, userByEmail.CodeExpiry,
|
||||
userByEmail.OTPEnabled, userByEmail.OTPVerified, userByEmail.OTPValidated, userByEmail.OTPSecret,
|
||||
userByEmail.OTPAuthURL, userByEmail.OTPBackupCodeHash, userByEmail.OTPBackupCodeHashAlgorithm,
|
||||
userByEmail.CreatedFromIPAddress, userByEmail.CreatedFromIPTimestamp, userByEmail.CreatedByUserID, userByEmail.CreatedByName,
|
||||
userByEmail.ModifiedFromIPAddress, userByEmail.ModifiedFromIPTimestamp, userByEmail.ModifiedByUserID, userByEmail.ModifiedAt, userByEmail.ModifiedByName,
|
||||
userByEmail.LastLoginAt, userByEmail.CreatedAt, userByEmail.UpdatedAt)
|
||||
|
||||
// Insert into users_by_date table
|
||||
batch.Query(`INSERT INTO users_by_date (tenant_id, created_date, id, email, first_name, last_name, name, lexical_name, timezone, role, status,
|
||||
phone, country, region, city, postal_code, address_line1, address_line2,
|
||||
has_shipping_address, shipping_name, shipping_phone, shipping_country, shipping_region,
|
||||
shipping_city, shipping_postal_code, shipping_address_line1, shipping_address_line2, profile_timezone,
|
||||
agree_terms_of_service, agree_promotions, agree_to_tracking_across_third_party_apps_and_services,
|
||||
password_hash_algorithm, password_hash, was_email_verified, code, code_type, code_expiry,
|
||||
otp_enabled, otp_verified, otp_validated, otp_secret, otp_auth_url, otp_backup_code_hash, otp_backup_code_hash_algorithm,
|
||||
created_from_ip_address, created_from_ip_timestamp, created_by_user_id, created_by_name,
|
||||
modified_from_ip_address, modified_from_ip_timestamp, modified_by_user_id, modified_at, modified_by_name, last_login_at,
|
||||
created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
userByDate.TenantID, userByDate.CreatedDate, userByDate.ID, userByDate.Email, userByDate.FirstName, userByDate.LastName,
|
||||
userByDate.Name, userByDate.LexicalName, userByDate.Timezone, userByDate.Role, userByDate.Status,
|
||||
userByDate.Phone, userByDate.Country, userByDate.Region, userByDate.City, userByDate.PostalCode,
|
||||
userByDate.AddressLine1, userByDate.AddressLine2, userByDate.HasShippingAddress, userByDate.ShippingName,
|
||||
userByDate.ShippingPhone, userByDate.ShippingCountry, userByDate.ShippingRegion, userByDate.ShippingCity,
|
||||
userByDate.ShippingPostalCode, userByDate.ShippingAddressLine1, userByDate.ShippingAddressLine2, userByDate.ProfileTimezone,
|
||||
userByDate.AgreeTermsOfService, userByDate.AgreePromotions, userByDate.AgreeToTrackingAcrossThirdPartyAppsAndServices,
|
||||
userByDate.PasswordHashAlgorithm, userByDate.PasswordHash, userByDate.WasEmailVerified,
|
||||
userByDate.Code, userByDate.CodeType, userByDate.CodeExpiry,
|
||||
userByDate.OTPEnabled, userByDate.OTPVerified, userByDate.OTPValidated, userByDate.OTPSecret,
|
||||
userByDate.OTPAuthURL, userByDate.OTPBackupCodeHash, userByDate.OTPBackupCodeHashAlgorithm,
|
||||
userByDate.CreatedFromIPAddress, userByDate.CreatedFromIPTimestamp, userByDate.CreatedByUserID, userByDate.CreatedByName,
|
||||
userByDate.ModifiedFromIPAddress, userByDate.ModifiedFromIPTimestamp, userByDate.ModifiedByUserID, userByDate.ModifiedAt, userByDate.ModifiedByName,
|
||||
userByDate.LastLoginAt, userByDate.CreatedAt, userByDate.UpdatedAt)
|
||||
|
||||
// Execute batch atomically
|
||||
if err := r.session.ExecuteBatch(batch); err != nil {
|
||||
r.logger.Error("failed to create user", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("user created successfully", zap.String("user_id", u.ID))
|
||||
return nil
|
||||
}
|
||||
47
cloud/maplepress-backend/internal/repository/user/delete.go
Normal file
47
cloud/maplepress-backend/internal/repository/user/delete.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Delete deletes a user from all tables using batched writes
|
||||
func (r *repository) Delete(ctx context.Context, tenantID string, id string) error {
|
||||
r.logger.Info("deleting user",
|
||||
zap.String("tenant_id", tenantID),
|
||||
zap.String("id", id))
|
||||
|
||||
// First, get the user to retrieve email and created_date for deleting from other tables
|
||||
user, err := r.GetByID(ctx, tenantID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createdDate := user.CreatedAt.Format("2006-01-02")
|
||||
|
||||
// Use batched writes to maintain consistency across all tables
|
||||
batch := r.session.NewBatch(gocql.LoggedBatch)
|
||||
|
||||
// Delete from users_by_id table
|
||||
batch.Query(`DELETE FROM users_by_id WHERE tenant_id = ? AND id = ?`,
|
||||
tenantID, id)
|
||||
|
||||
// Delete from users_by_email table
|
||||
batch.Query(`DELETE FROM users_by_email WHERE tenant_id = ? AND email = ?`,
|
||||
tenantID, user.Email)
|
||||
|
||||
// Delete from users_by_date table
|
||||
batch.Query(`DELETE FROM users_by_date WHERE tenant_id = ? AND created_date = ? AND id = ?`,
|
||||
tenantID, createdDate, id)
|
||||
|
||||
// Execute batch atomically
|
||||
if err := r.session.ExecuteBatch(batch); err != nil {
|
||||
r.logger.Error("failed to delete user", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("user deleted successfully", zap.String("user_id", id))
|
||||
return nil
|
||||
}
|
||||
230
cloud/maplepress-backend/internal/repository/user/get.go
Normal file
230
cloud/maplepress-backend/internal/repository/user/get.go
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"go.uber.org/zap"
|
||||
|
||||
domainuser "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/repository/user/models"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/logger"
|
||||
)
|
||||
|
||||
// GetByID retrieves a user by ID from the users_by_id table
|
||||
func (r *repository) GetByID(ctx context.Context, tenantID string, id string) (*domainuser.User, error) {
|
||||
r.logger.Debug("getting user by ID",
|
||||
zap.String("tenant_id", tenantID),
|
||||
zap.String("id", id))
|
||||
|
||||
// EXPLICIT: We're querying the users_by_id table with tenant isolation
|
||||
var userByID models.UserByID
|
||||
|
||||
query := `SELECT tenant_id, id, email, first_name, last_name, name, lexical_name, timezone, role, status,
|
||||
phone, country, region, city, postal_code, address_line1, address_line2,
|
||||
has_shipping_address, shipping_name, shipping_phone, shipping_country, shipping_region,
|
||||
shipping_city, shipping_postal_code, shipping_address_line1, shipping_address_line2,
|
||||
profile_timezone, agree_terms_of_service, agree_promotions, agree_to_tracking_across_third_party_apps_and_services,
|
||||
password_hash_algorithm, password_hash, was_email_verified, code, code_type, code_expiry,
|
||||
otp_enabled, otp_verified, otp_validated, otp_secret, otp_auth_url, otp_backup_code_hash, otp_backup_code_hash_algorithm,
|
||||
created_from_ip_address, created_from_ip_timestamp, created_by_user_id, created_by_name,
|
||||
modified_from_ip_address, modified_from_ip_timestamp, modified_by_user_id, modified_at, modified_by_name, last_login_at,
|
||||
created_at, updated_at
|
||||
FROM users_by_id
|
||||
WHERE tenant_id = ? AND id = ?`
|
||||
|
||||
err := r.session.Query(query, tenantID, id).
|
||||
Consistency(gocql.Quorum).
|
||||
Scan(&userByID.TenantID, &userByID.ID, &userByID.Email, &userByID.FirstName, &userByID.LastName,
|
||||
&userByID.Name, &userByID.LexicalName, &userByID.Timezone, &userByID.Role, &userByID.Status,
|
||||
&userByID.Phone, &userByID.Country, &userByID.Region, &userByID.City, &userByID.PostalCode,
|
||||
&userByID.AddressLine1, &userByID.AddressLine2, &userByID.HasShippingAddress, &userByID.ShippingName,
|
||||
&userByID.ShippingPhone, &userByID.ShippingCountry, &userByID.ShippingRegion, &userByID.ShippingCity,
|
||||
&userByID.ShippingPostalCode, &userByID.ShippingAddressLine1, &userByID.ShippingAddressLine2,
|
||||
&userByID.ProfileTimezone, &userByID.AgreeTermsOfService, &userByID.AgreePromotions, &userByID.AgreeToTrackingAcrossThirdPartyAppsAndServices,
|
||||
&userByID.PasswordHashAlgorithm, &userByID.PasswordHash, &userByID.WasEmailVerified, &userByID.Code,
|
||||
&userByID.CodeType, &userByID.CodeExpiry, &userByID.OTPEnabled, &userByID.OTPVerified, &userByID.OTPValidated,
|
||||
&userByID.OTPSecret, &userByID.OTPAuthURL, &userByID.OTPBackupCodeHash, &userByID.OTPBackupCodeHashAlgorithm,
|
||||
&userByID.CreatedFromIPAddress, &userByID.CreatedFromIPTimestamp, &userByID.CreatedByUserID, &userByID.CreatedByName,
|
||||
&userByID.ModifiedFromIPAddress, &userByID.ModifiedFromIPTimestamp, &userByID.ModifiedByUserID, &userByID.ModifiedAt, &userByID.ModifiedByName, &userByID.LastLoginAt,
|
||||
&userByID.CreatedAt, &userByID.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
if err == gocql.ErrNotFound {
|
||||
return nil, domainuser.ErrUserNotFound
|
||||
}
|
||||
r.logger.Error("failed to get user by ID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert table model to domain entity
|
||||
return userByID.ToUser(), nil
|
||||
}
|
||||
|
||||
// GetByEmail retrieves a user by email from the users_by_email table
|
||||
func (r *repository) GetByEmail(ctx context.Context, tenantID string, email string) (*domainuser.User, error) {
|
||||
// CWE-532: Use redacted email for logging
|
||||
r.logger.Debug("getting user by email",
|
||||
zap.String("tenant_id", tenantID),
|
||||
logger.EmailHash(email),
|
||||
logger.SafeEmail("email_redacted", email))
|
||||
|
||||
// EXPLICIT: We're querying the users_by_email table with tenant isolation
|
||||
var userByEmail models.UserByEmail
|
||||
|
||||
query := `SELECT tenant_id, email, id, first_name, last_name, name, lexical_name, timezone, role, status,
|
||||
phone, country, region, city, postal_code, address_line1, address_line2,
|
||||
has_shipping_address, shipping_name, shipping_phone, shipping_country, shipping_region,
|
||||
shipping_city, shipping_postal_code, shipping_address_line1, shipping_address_line2,
|
||||
profile_timezone, agree_terms_of_service, agree_promotions, agree_to_tracking_across_third_party_apps_and_services,
|
||||
password_hash_algorithm, password_hash, was_email_verified, code, code_type, code_expiry,
|
||||
otp_enabled, otp_verified, otp_validated, otp_secret, otp_auth_url, otp_backup_code_hash, otp_backup_code_hash_algorithm,
|
||||
created_from_ip_address, created_from_ip_timestamp, created_by_user_id, created_by_name,
|
||||
modified_from_ip_address, modified_from_ip_timestamp, modified_by_user_id, modified_at, modified_by_name, last_login_at,
|
||||
created_at, updated_at
|
||||
FROM users_by_email
|
||||
WHERE tenant_id = ? AND email = ?`
|
||||
|
||||
err := r.session.Query(query, tenantID, email).
|
||||
Consistency(gocql.Quorum).
|
||||
Scan(&userByEmail.TenantID, &userByEmail.Email, &userByEmail.ID, &userByEmail.FirstName, &userByEmail.LastName,
|
||||
&userByEmail.Name, &userByEmail.LexicalName, &userByEmail.Timezone, &userByEmail.Role, &userByEmail.Status,
|
||||
&userByEmail.Phone, &userByEmail.Country, &userByEmail.Region, &userByEmail.City, &userByEmail.PostalCode,
|
||||
&userByEmail.AddressLine1, &userByEmail.AddressLine2, &userByEmail.HasShippingAddress, &userByEmail.ShippingName,
|
||||
&userByEmail.ShippingPhone, &userByEmail.ShippingCountry, &userByEmail.ShippingRegion, &userByEmail.ShippingCity,
|
||||
&userByEmail.ShippingPostalCode, &userByEmail.ShippingAddressLine1, &userByEmail.ShippingAddressLine2,
|
||||
&userByEmail.ProfileTimezone, &userByEmail.AgreeTermsOfService, &userByEmail.AgreePromotions, &userByEmail.AgreeToTrackingAcrossThirdPartyAppsAndServices,
|
||||
&userByEmail.PasswordHashAlgorithm, &userByEmail.PasswordHash, &userByEmail.WasEmailVerified, &userByEmail.Code,
|
||||
&userByEmail.CodeType, &userByEmail.CodeExpiry, &userByEmail.OTPEnabled, &userByEmail.OTPVerified, &userByEmail.OTPValidated,
|
||||
&userByEmail.OTPSecret, &userByEmail.OTPAuthURL, &userByEmail.OTPBackupCodeHash, &userByEmail.OTPBackupCodeHashAlgorithm,
|
||||
&userByEmail.CreatedFromIPAddress, &userByEmail.CreatedFromIPTimestamp, &userByEmail.CreatedByUserID, &userByEmail.CreatedByName,
|
||||
&userByEmail.ModifiedFromIPAddress, &userByEmail.ModifiedFromIPTimestamp, &userByEmail.ModifiedByUserID, &userByEmail.ModifiedAt, &userByEmail.ModifiedByName, &userByEmail.LastLoginAt,
|
||||
&userByEmail.CreatedAt, &userByEmail.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
if err == gocql.ErrNotFound {
|
||||
return nil, domainuser.ErrUserNotFound
|
||||
}
|
||||
r.logger.Error("failed to get user by email", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert table model to domain entity
|
||||
return userByEmail.ToUser(), nil
|
||||
}
|
||||
|
||||
// GetByEmailGlobal retrieves a user by email across all tenants (for login)
|
||||
// WARNING: This bypasses tenant isolation and should ONLY be used for authentication
|
||||
func (r *repository) GetByEmailGlobal(ctx context.Context, email string) (*domainuser.User, error) {
|
||||
// CWE-532: Use redacted email for logging
|
||||
r.logger.Debug("getting user by email globally (no tenant filter)",
|
||||
logger.EmailHash(email),
|
||||
logger.SafeEmail("email_redacted", email))
|
||||
|
||||
// EXPLICIT: Querying users_by_email WITHOUT tenant_id filter
|
||||
// This allows login with just email/password, finding the user's tenant automatically
|
||||
var userByEmail models.UserByEmail
|
||||
|
||||
query := `SELECT tenant_id, email, id, first_name, last_name, name, lexical_name, timezone, role, status,
|
||||
phone, country, region, city, postal_code, address_line1, address_line2,
|
||||
has_shipping_address, shipping_name, shipping_phone, shipping_country, shipping_region,
|
||||
shipping_city, shipping_postal_code, shipping_address_line1, shipping_address_line2,
|
||||
profile_timezone, agree_terms_of_service, agree_promotions, agree_to_tracking_across_third_party_apps_and_services,
|
||||
password_hash_algorithm, password_hash, was_email_verified, code, code_type, code_expiry,
|
||||
otp_enabled, otp_verified, otp_validated, otp_secret, otp_auth_url, otp_backup_code_hash, otp_backup_code_hash_algorithm,
|
||||
created_from_ip_address, created_from_ip_timestamp, created_by_user_id, created_by_name,
|
||||
modified_from_ip_address, modified_from_ip_timestamp, modified_by_user_id, modified_at, modified_by_name, last_login_at,
|
||||
created_at, updated_at
|
||||
FROM users_by_email
|
||||
WHERE email = ?
|
||||
LIMIT 1
|
||||
ALLOW FILTERING`
|
||||
|
||||
err := r.session.Query(query, email).
|
||||
Consistency(gocql.Quorum).
|
||||
Scan(&userByEmail.TenantID, &userByEmail.Email, &userByEmail.ID, &userByEmail.FirstName, &userByEmail.LastName,
|
||||
&userByEmail.Name, &userByEmail.LexicalName, &userByEmail.Timezone, &userByEmail.Role, &userByEmail.Status,
|
||||
&userByEmail.Phone, &userByEmail.Country, &userByEmail.Region, &userByEmail.City, &userByEmail.PostalCode,
|
||||
&userByEmail.AddressLine1, &userByEmail.AddressLine2, &userByEmail.HasShippingAddress, &userByEmail.ShippingName,
|
||||
&userByEmail.ShippingPhone, &userByEmail.ShippingCountry, &userByEmail.ShippingRegion, &userByEmail.ShippingCity,
|
||||
&userByEmail.ShippingPostalCode, &userByEmail.ShippingAddressLine1, &userByEmail.ShippingAddressLine2,
|
||||
&userByEmail.ProfileTimezone, &userByEmail.AgreeTermsOfService, &userByEmail.AgreePromotions, &userByEmail.AgreeToTrackingAcrossThirdPartyAppsAndServices,
|
||||
&userByEmail.PasswordHashAlgorithm, &userByEmail.PasswordHash, &userByEmail.WasEmailVerified, &userByEmail.Code,
|
||||
&userByEmail.CodeType, &userByEmail.CodeExpiry, &userByEmail.OTPEnabled, &userByEmail.OTPVerified, &userByEmail.OTPValidated,
|
||||
&userByEmail.OTPSecret, &userByEmail.OTPAuthURL, &userByEmail.OTPBackupCodeHash, &userByEmail.OTPBackupCodeHashAlgorithm,
|
||||
&userByEmail.CreatedFromIPAddress, &userByEmail.CreatedFromIPTimestamp, &userByEmail.CreatedByUserID, &userByEmail.CreatedByName,
|
||||
&userByEmail.ModifiedFromIPAddress, &userByEmail.ModifiedFromIPTimestamp, &userByEmail.ModifiedByUserID, &userByEmail.ModifiedAt, &userByEmail.ModifiedByName, &userByEmail.LastLoginAt,
|
||||
&userByEmail.CreatedAt, &userByEmail.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
if err == gocql.ErrNotFound {
|
||||
return nil, domainuser.ErrUserNotFound
|
||||
}
|
||||
r.logger.Error("failed to get user by email globally", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// CWE-532: Use redacted email for logging
|
||||
r.logger.Info("found user by email globally",
|
||||
logger.EmailHash(email),
|
||||
logger.SafeEmail("email_redacted", email),
|
||||
zap.String("tenant_id", userByEmail.TenantID))
|
||||
|
||||
// Convert table model to domain entity
|
||||
return userByEmail.ToUser(), nil
|
||||
}
|
||||
|
||||
// ListByDate lists users created within a date range from the users_by_date table
|
||||
func (r *repository) ListByDate(ctx context.Context, tenantID string, startDate, endDate string, limit int) ([]*domainuser.User, error) {
|
||||
r.logger.Debug("listing users by date range",
|
||||
zap.String("tenant_id", tenantID),
|
||||
zap.String("start_date", startDate),
|
||||
zap.String("end_date", endDate),
|
||||
zap.Int("limit", limit))
|
||||
|
||||
// EXPLICIT: We're querying the users_by_date table
|
||||
query := `SELECT tenant_id, created_date, id, email, first_name, last_name, name, lexical_name, timezone, role, status,
|
||||
phone, country, region, city, postal_code, address_line1, address_line2,
|
||||
has_shipping_address, shipping_name, shipping_phone, shipping_country, shipping_region,
|
||||
shipping_city, shipping_postal_code, shipping_address_line1, shipping_address_line2,
|
||||
profile_timezone, agree_terms_of_service, agree_promotions, agree_to_tracking_across_third_party_apps_and_services,
|
||||
password_hash_algorithm, password_hash, was_email_verified, code, code_type, code_expiry,
|
||||
otp_enabled, otp_verified, otp_validated, otp_secret, otp_auth_url, otp_backup_code_hash, otp_backup_code_hash_algorithm,
|
||||
created_from_ip_address, created_from_ip_timestamp, created_by_user_id, created_by_name,
|
||||
modified_from_ip_address, modified_from_ip_timestamp, modified_by_user_id, modified_at, modified_by_name, last_login_at,
|
||||
created_at, updated_at
|
||||
FROM users_by_date
|
||||
WHERE tenant_id = ? AND created_date >= ? AND created_date <= ?
|
||||
LIMIT ?`
|
||||
|
||||
iter := r.session.Query(query, tenantID, startDate, endDate, limit).
|
||||
Consistency(gocql.Quorum).
|
||||
Iter()
|
||||
|
||||
var users []*domainuser.User
|
||||
var userByDate models.UserByDate
|
||||
|
||||
for iter.Scan(&userByDate.TenantID, &userByDate.CreatedDate, &userByDate.ID, &userByDate.Email,
|
||||
&userByDate.FirstName, &userByDate.LastName, &userByDate.Name, &userByDate.LexicalName, &userByDate.Timezone,
|
||||
&userByDate.Role, &userByDate.Status, &userByDate.Phone, &userByDate.Country, &userByDate.Region,
|
||||
&userByDate.City, &userByDate.PostalCode, &userByDate.AddressLine1, &userByDate.AddressLine2,
|
||||
&userByDate.HasShippingAddress, &userByDate.ShippingName, &userByDate.ShippingPhone, &userByDate.ShippingCountry,
|
||||
&userByDate.ShippingRegion, &userByDate.ShippingCity, &userByDate.ShippingPostalCode, &userByDate.ShippingAddressLine1,
|
||||
&userByDate.ShippingAddressLine2, &userByDate.ProfileTimezone, &userByDate.AgreeTermsOfService, &userByDate.AgreePromotions,
|
||||
&userByDate.AgreeToTrackingAcrossThirdPartyAppsAndServices, &userByDate.PasswordHashAlgorithm, &userByDate.PasswordHash,
|
||||
&userByDate.WasEmailVerified, &userByDate.Code, &userByDate.CodeType, &userByDate.CodeExpiry, &userByDate.OTPEnabled,
|
||||
&userByDate.OTPVerified, &userByDate.OTPValidated, &userByDate.OTPSecret, &userByDate.OTPAuthURL,
|
||||
&userByDate.OTPBackupCodeHash, &userByDate.OTPBackupCodeHashAlgorithm, &userByDate.CreatedFromIPAddress,
|
||||
&userByDate.CreatedFromIPTimestamp, &userByDate.CreatedByUserID, &userByDate.CreatedByName, &userByDate.ModifiedFromIPAddress,
|
||||
&userByDate.ModifiedFromIPTimestamp, &userByDate.ModifiedByUserID, &userByDate.ModifiedAt, &userByDate.ModifiedByName, &userByDate.LastLoginAt,
|
||||
&userByDate.CreatedAt, &userByDate.UpdatedAt) {
|
||||
users = append(users, userByDate.ToUser())
|
||||
}
|
||||
|
||||
if err := iter.Close(); err != nil {
|
||||
r.logger.Error("failed to list users by date", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
22
cloud/maplepress-backend/internal/repository/user/impl.go
Normal file
22
cloud/maplepress-backend/internal/repository/user/impl.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"github.com/gocql/gocql"
|
||||
"go.uber.org/zap"
|
||||
|
||||
domainuser "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
|
||||
)
|
||||
|
||||
// repository implements the user.Repository interface
|
||||
type repository struct {
|
||||
session *gocql.Session
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// ProvideRepository creates a new user repository
|
||||
func ProvideRepository(session *gocql.Session, logger *zap.Logger) domainuser.Repository {
|
||||
return &repository{
|
||||
session: session,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
|
||||
)
|
||||
|
||||
// UserByDate represents the users_by_date table
|
||||
// Query pattern: List users sorted by creation date
|
||||
// Primary key: ((tenant_id, created_date), id) - composite partition key + clustering
|
||||
type UserByDate struct {
|
||||
TenantID string `db:"tenant_id"` // Multi-tenant isolation (partition key part 1)
|
||||
CreatedDate string `db:"created_date"` // Format: YYYY-MM-DD (partition key part 2)
|
||||
ID string `db:"id"` // Clustering column
|
||||
Email string `db:"email"`
|
||||
FirstName string `db:"first_name"`
|
||||
LastName string `db:"last_name"`
|
||||
Name string `db:"name"`
|
||||
LexicalName string `db:"lexical_name"`
|
||||
Timezone string `db:"timezone"`
|
||||
Role int `db:"role"`
|
||||
Status int `db:"status"`
|
||||
|
||||
// Profile data fields (flattened)
|
||||
Phone string `db:"phone"`
|
||||
Country string `db:"country"`
|
||||
Region string `db:"region"`
|
||||
City string `db:"city"`
|
||||
PostalCode string `db:"postal_code"`
|
||||
AddressLine1 string `db:"address_line1"`
|
||||
AddressLine2 string `db:"address_line2"`
|
||||
HasShippingAddress bool `db:"has_shipping_address"`
|
||||
ShippingName string `db:"shipping_name"`
|
||||
ShippingPhone string `db:"shipping_phone"`
|
||||
ShippingCountry string `db:"shipping_country"`
|
||||
ShippingRegion string `db:"shipping_region"`
|
||||
ShippingCity string `db:"shipping_city"`
|
||||
ShippingPostalCode string `db:"shipping_postal_code"`
|
||||
ShippingAddressLine1 string `db:"shipping_address_line1"`
|
||||
ShippingAddressLine2 string `db:"shipping_address_line2"`
|
||||
ProfileTimezone string `db:"profile_timezone"`
|
||||
AgreeTermsOfService bool `db:"agree_terms_of_service"`
|
||||
AgreePromotions bool `db:"agree_promotions"`
|
||||
AgreeToTrackingAcrossThirdPartyAppsAndServices bool `db:"agree_to_tracking_across_third_party_apps_and_services"`
|
||||
|
||||
// Security data fields (flattened)
|
||||
PasswordHashAlgorithm string `db:"password_hash_algorithm"`
|
||||
PasswordHash string `db:"password_hash"`
|
||||
WasEmailVerified bool `db:"was_email_verified"`
|
||||
Code string `db:"code"`
|
||||
CodeType string `db:"code_type"`
|
||||
CodeExpiry time.Time `db:"code_expiry"`
|
||||
OTPEnabled bool `db:"otp_enabled"`
|
||||
OTPVerified bool `db:"otp_verified"`
|
||||
OTPValidated bool `db:"otp_validated"`
|
||||
OTPSecret string `db:"otp_secret"`
|
||||
OTPAuthURL string `db:"otp_auth_url"`
|
||||
OTPBackupCodeHash string `db:"otp_backup_code_hash"`
|
||||
OTPBackupCodeHashAlgorithm string `db:"otp_backup_code_hash_algorithm"`
|
||||
|
||||
// Metadata fields (flattened)
|
||||
// CWE-359: Encrypted IP addresses for GDPR compliance
|
||||
CreatedFromIPAddress string `db:"created_from_ip_address"` // Encrypted with go-ipcrypt
|
||||
CreatedFromIPTimestamp time.Time `db:"created_from_ip_timestamp"` // For 90-day expiration tracking
|
||||
CreatedByUserID string `db:"created_by_user_id"`
|
||||
CreatedByName string `db:"created_by_name"`
|
||||
ModifiedFromIPAddress string `db:"modified_from_ip_address"` // Encrypted with go-ipcrypt
|
||||
ModifiedFromIPTimestamp time.Time `db:"modified_from_ip_timestamp"` // For 90-day expiration tracking
|
||||
ModifiedByUserID string `db:"modified_by_user_id"`
|
||||
ModifiedAt time.Time `db:"modified_at"`
|
||||
ModifiedByName string `db:"modified_by_name"`
|
||||
LastLoginAt time.Time `db:"last_login_at"`
|
||||
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
// ToUser converts table model to domain entity
|
||||
func (u *UserByDate) ToUser() *user.User {
|
||||
return &user.User{
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
Name: u.Name,
|
||||
LexicalName: u.LexicalName,
|
||||
Timezone: u.Timezone,
|
||||
Role: u.Role,
|
||||
Status: u.Status,
|
||||
|
||||
ProfileData: &user.UserProfileData{
|
||||
Phone: u.Phone,
|
||||
Country: u.Country,
|
||||
Region: u.Region,
|
||||
City: u.City,
|
||||
PostalCode: u.PostalCode,
|
||||
AddressLine1: u.AddressLine1,
|
||||
AddressLine2: u.AddressLine2,
|
||||
HasShippingAddress: u.HasShippingAddress,
|
||||
ShippingName: u.ShippingName,
|
||||
ShippingPhone: u.ShippingPhone,
|
||||
ShippingCountry: u.ShippingCountry,
|
||||
ShippingRegion: u.ShippingRegion,
|
||||
ShippingCity: u.ShippingCity,
|
||||
ShippingPostalCode: u.ShippingPostalCode,
|
||||
ShippingAddressLine1: u.ShippingAddressLine1,
|
||||
ShippingAddressLine2: u.ShippingAddressLine2,
|
||||
Timezone: u.ProfileTimezone,
|
||||
AgreeTermsOfService: u.AgreeTermsOfService,
|
||||
AgreePromotions: u.AgreePromotions,
|
||||
AgreeToTrackingAcrossThirdPartyAppsAndServices: u.AgreeToTrackingAcrossThirdPartyAppsAndServices,
|
||||
},
|
||||
|
||||
SecurityData: &user.UserSecurityData{
|
||||
PasswordHashAlgorithm: u.PasswordHashAlgorithm,
|
||||
PasswordHash: u.PasswordHash,
|
||||
WasEmailVerified: u.WasEmailVerified,
|
||||
Code: u.Code,
|
||||
CodeType: u.CodeType,
|
||||
CodeExpiry: u.CodeExpiry,
|
||||
OTPEnabled: u.OTPEnabled,
|
||||
OTPVerified: u.OTPVerified,
|
||||
OTPValidated: u.OTPValidated,
|
||||
OTPSecret: u.OTPSecret,
|
||||
OTPAuthURL: u.OTPAuthURL,
|
||||
OTPBackupCodeHash: u.OTPBackupCodeHash,
|
||||
OTPBackupCodeHashAlgorithm: u.OTPBackupCodeHashAlgorithm,
|
||||
},
|
||||
|
||||
Metadata: &user.UserMetadata{
|
||||
CreatedFromIPAddress: u.CreatedFromIPAddress,
|
||||
CreatedFromIPTimestamp: u.CreatedFromIPTimestamp,
|
||||
CreatedByUserID: u.CreatedByUserID,
|
||||
CreatedAt: u.CreatedAt,
|
||||
CreatedByName: u.CreatedByName,
|
||||
ModifiedFromIPAddress: u.ModifiedFromIPAddress,
|
||||
ModifiedFromIPTimestamp: u.ModifiedFromIPTimestamp,
|
||||
ModifiedByUserID: u.ModifiedByUserID,
|
||||
ModifiedAt: u.ModifiedAt,
|
||||
ModifiedByName: u.ModifiedByName,
|
||||
LastLoginAt: u.LastLoginAt,
|
||||
},
|
||||
|
||||
TenantID: u.TenantID,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// FromUserByDate converts domain entity to table model
|
||||
func FromUserByDate(tenantID string, u *user.User) *UserByDate {
|
||||
userByDate := &UserByDate{
|
||||
TenantID: tenantID,
|
||||
CreatedDate: u.CreatedAt.Format("2006-01-02"),
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
Name: u.Name,
|
||||
LexicalName: u.LexicalName,
|
||||
Timezone: u.Timezone,
|
||||
Role: u.Role,
|
||||
Status: u.Status,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
}
|
||||
|
||||
// Map ProfileData if present
|
||||
if u.ProfileData != nil {
|
||||
userByDate.Phone = u.ProfileData.Phone
|
||||
userByDate.Country = u.ProfileData.Country
|
||||
userByDate.Region = u.ProfileData.Region
|
||||
userByDate.City = u.ProfileData.City
|
||||
userByDate.PostalCode = u.ProfileData.PostalCode
|
||||
userByDate.AddressLine1 = u.ProfileData.AddressLine1
|
||||
userByDate.AddressLine2 = u.ProfileData.AddressLine2
|
||||
userByDate.HasShippingAddress = u.ProfileData.HasShippingAddress
|
||||
userByDate.ShippingName = u.ProfileData.ShippingName
|
||||
userByDate.ShippingPhone = u.ProfileData.ShippingPhone
|
||||
userByDate.ShippingCountry = u.ProfileData.ShippingCountry
|
||||
userByDate.ShippingRegion = u.ProfileData.ShippingRegion
|
||||
userByDate.ShippingCity = u.ProfileData.ShippingCity
|
||||
userByDate.ShippingPostalCode = u.ProfileData.ShippingPostalCode
|
||||
userByDate.ShippingAddressLine1 = u.ProfileData.ShippingAddressLine1
|
||||
userByDate.ShippingAddressLine2 = u.ProfileData.ShippingAddressLine2
|
||||
userByDate.ProfileTimezone = u.ProfileData.Timezone
|
||||
userByDate.AgreeTermsOfService = u.ProfileData.AgreeTermsOfService
|
||||
userByDate.AgreePromotions = u.ProfileData.AgreePromotions
|
||||
userByDate.AgreeToTrackingAcrossThirdPartyAppsAndServices = u.ProfileData.AgreeToTrackingAcrossThirdPartyAppsAndServices
|
||||
}
|
||||
|
||||
// Map SecurityData if present
|
||||
if u.SecurityData != nil {
|
||||
userByDate.PasswordHashAlgorithm = u.SecurityData.PasswordHashAlgorithm
|
||||
userByDate.PasswordHash = u.SecurityData.PasswordHash
|
||||
userByDate.WasEmailVerified = u.SecurityData.WasEmailVerified
|
||||
userByDate.Code = u.SecurityData.Code
|
||||
userByDate.CodeType = u.SecurityData.CodeType
|
||||
userByDate.CodeExpiry = u.SecurityData.CodeExpiry
|
||||
userByDate.OTPEnabled = u.SecurityData.OTPEnabled
|
||||
userByDate.OTPVerified = u.SecurityData.OTPVerified
|
||||
userByDate.OTPValidated = u.SecurityData.OTPValidated
|
||||
userByDate.OTPSecret = u.SecurityData.OTPSecret
|
||||
userByDate.OTPAuthURL = u.SecurityData.OTPAuthURL
|
||||
userByDate.OTPBackupCodeHash = u.SecurityData.OTPBackupCodeHash
|
||||
userByDate.OTPBackupCodeHashAlgorithm = u.SecurityData.OTPBackupCodeHashAlgorithm
|
||||
}
|
||||
|
||||
// Map Metadata if present
|
||||
if u.Metadata != nil {
|
||||
userByDate.CreatedFromIPAddress = u.Metadata.CreatedFromIPAddress
|
||||
userByDate.CreatedFromIPTimestamp = u.Metadata.CreatedFromIPTimestamp
|
||||
userByDate.CreatedByUserID = u.Metadata.CreatedByUserID
|
||||
userByDate.CreatedByName = u.Metadata.CreatedByName
|
||||
userByDate.ModifiedFromIPAddress = u.Metadata.ModifiedFromIPAddress
|
||||
userByDate.ModifiedFromIPTimestamp = u.Metadata.ModifiedFromIPTimestamp
|
||||
userByDate.ModifiedByUserID = u.Metadata.ModifiedByUserID
|
||||
userByDate.ModifiedAt = u.Metadata.ModifiedAt
|
||||
userByDate.ModifiedByName = u.Metadata.ModifiedByName
|
||||
userByDate.LastLoginAt = u.Metadata.LastLoginAt
|
||||
}
|
||||
|
||||
return userByDate
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
|
||||
)
|
||||
|
||||
// UserByEmail represents the users_by_email table
|
||||
// Query pattern: Get user by email (for login, uniqueness checks)
|
||||
// Primary key: (tenant_id, email) - composite partition key for multi-tenancy
|
||||
type UserByEmail struct {
|
||||
TenantID string `db:"tenant_id"` // Multi-tenant isolation
|
||||
Email string `db:"email"`
|
||||
ID string `db:"id"`
|
||||
FirstName string `db:"first_name"`
|
||||
LastName string `db:"last_name"`
|
||||
Name string `db:"name"`
|
||||
LexicalName string `db:"lexical_name"`
|
||||
Timezone string `db:"timezone"`
|
||||
Role int `db:"role"`
|
||||
Status int `db:"status"`
|
||||
|
||||
// Profile data fields (flattened)
|
||||
Phone string `db:"phone"`
|
||||
Country string `db:"country"`
|
||||
Region string `db:"region"`
|
||||
City string `db:"city"`
|
||||
PostalCode string `db:"postal_code"`
|
||||
AddressLine1 string `db:"address_line1"`
|
||||
AddressLine2 string `db:"address_line2"`
|
||||
HasShippingAddress bool `db:"has_shipping_address"`
|
||||
ShippingName string `db:"shipping_name"`
|
||||
ShippingPhone string `db:"shipping_phone"`
|
||||
ShippingCountry string `db:"shipping_country"`
|
||||
ShippingRegion string `db:"shipping_region"`
|
||||
ShippingCity string `db:"shipping_city"`
|
||||
ShippingPostalCode string `db:"shipping_postal_code"`
|
||||
ShippingAddressLine1 string `db:"shipping_address_line1"`
|
||||
ShippingAddressLine2 string `db:"shipping_address_line2"`
|
||||
ProfileTimezone string `db:"profile_timezone"`
|
||||
AgreeTermsOfService bool `db:"agree_terms_of_service"`
|
||||
AgreePromotions bool `db:"agree_promotions"`
|
||||
AgreeToTrackingAcrossThirdPartyAppsAndServices bool `db:"agree_to_tracking_across_third_party_apps_and_services"`
|
||||
|
||||
// Security data fields (flattened)
|
||||
PasswordHashAlgorithm string `db:"password_hash_algorithm"`
|
||||
PasswordHash string `db:"password_hash"`
|
||||
WasEmailVerified bool `db:"was_email_verified"`
|
||||
Code string `db:"code"`
|
||||
CodeType string `db:"code_type"`
|
||||
CodeExpiry time.Time `db:"code_expiry"`
|
||||
OTPEnabled bool `db:"otp_enabled"`
|
||||
OTPVerified bool `db:"otp_verified"`
|
||||
OTPValidated bool `db:"otp_validated"`
|
||||
OTPSecret string `db:"otp_secret"`
|
||||
OTPAuthURL string `db:"otp_auth_url"`
|
||||
OTPBackupCodeHash string `db:"otp_backup_code_hash"`
|
||||
OTPBackupCodeHashAlgorithm string `db:"otp_backup_code_hash_algorithm"`
|
||||
|
||||
// Metadata fields (flattened)
|
||||
// CWE-359: Encrypted IP addresses for GDPR compliance
|
||||
CreatedFromIPAddress string `db:"created_from_ip_address"` // Encrypted with go-ipcrypt
|
||||
CreatedFromIPTimestamp time.Time `db:"created_from_ip_timestamp"` // For 90-day expiration tracking
|
||||
CreatedByUserID string `db:"created_by_user_id"`
|
||||
CreatedByName string `db:"created_by_name"`
|
||||
ModifiedFromIPAddress string `db:"modified_from_ip_address"` // Encrypted with go-ipcrypt
|
||||
ModifiedFromIPTimestamp time.Time `db:"modified_from_ip_timestamp"` // For 90-day expiration tracking
|
||||
ModifiedByUserID string `db:"modified_by_user_id"`
|
||||
ModifiedAt time.Time `db:"modified_at"`
|
||||
ModifiedByName string `db:"modified_by_name"`
|
||||
LastLoginAt time.Time `db:"last_login_at"`
|
||||
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
// ToUser converts table model to domain entity
|
||||
func (u *UserByEmail) ToUser() *user.User {
|
||||
return &user.User{
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
Name: u.Name,
|
||||
LexicalName: u.LexicalName,
|
||||
Timezone: u.Timezone,
|
||||
Role: u.Role,
|
||||
Status: u.Status,
|
||||
|
||||
ProfileData: &user.UserProfileData{
|
||||
Phone: u.Phone,
|
||||
Country: u.Country,
|
||||
Region: u.Region,
|
||||
City: u.City,
|
||||
PostalCode: u.PostalCode,
|
||||
AddressLine1: u.AddressLine1,
|
||||
AddressLine2: u.AddressLine2,
|
||||
HasShippingAddress: u.HasShippingAddress,
|
||||
ShippingName: u.ShippingName,
|
||||
ShippingPhone: u.ShippingPhone,
|
||||
ShippingCountry: u.ShippingCountry,
|
||||
ShippingRegion: u.ShippingRegion,
|
||||
ShippingCity: u.ShippingCity,
|
||||
ShippingPostalCode: u.ShippingPostalCode,
|
||||
ShippingAddressLine1: u.ShippingAddressLine1,
|
||||
ShippingAddressLine2: u.ShippingAddressLine2,
|
||||
Timezone: u.ProfileTimezone,
|
||||
AgreeTermsOfService: u.AgreeTermsOfService,
|
||||
AgreePromotions: u.AgreePromotions,
|
||||
AgreeToTrackingAcrossThirdPartyAppsAndServices: u.AgreeToTrackingAcrossThirdPartyAppsAndServices,
|
||||
},
|
||||
|
||||
SecurityData: &user.UserSecurityData{
|
||||
PasswordHashAlgorithm: u.PasswordHashAlgorithm,
|
||||
PasswordHash: u.PasswordHash,
|
||||
WasEmailVerified: u.WasEmailVerified,
|
||||
Code: u.Code,
|
||||
CodeType: u.CodeType,
|
||||
CodeExpiry: u.CodeExpiry,
|
||||
OTPEnabled: u.OTPEnabled,
|
||||
OTPVerified: u.OTPVerified,
|
||||
OTPValidated: u.OTPValidated,
|
||||
OTPSecret: u.OTPSecret,
|
||||
OTPAuthURL: u.OTPAuthURL,
|
||||
OTPBackupCodeHash: u.OTPBackupCodeHash,
|
||||
OTPBackupCodeHashAlgorithm: u.OTPBackupCodeHashAlgorithm,
|
||||
},
|
||||
|
||||
Metadata: &user.UserMetadata{
|
||||
CreatedFromIPAddress: u.CreatedFromIPAddress,
|
||||
CreatedFromIPTimestamp: u.CreatedFromIPTimestamp,
|
||||
CreatedByUserID: u.CreatedByUserID,
|
||||
CreatedAt: u.CreatedAt,
|
||||
CreatedByName: u.CreatedByName,
|
||||
ModifiedFromIPAddress: u.ModifiedFromIPAddress,
|
||||
ModifiedFromIPTimestamp: u.ModifiedFromIPTimestamp,
|
||||
ModifiedByUserID: u.ModifiedByUserID,
|
||||
ModifiedAt: u.ModifiedAt,
|
||||
ModifiedByName: u.ModifiedByName,
|
||||
LastLoginAt: u.LastLoginAt,
|
||||
},
|
||||
|
||||
TenantID: u.TenantID,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// FromUserByEmail converts domain entity to table model
|
||||
func FromUserByEmail(tenantID string, u *user.User) *UserByEmail {
|
||||
userByEmail := &UserByEmail{
|
||||
TenantID: tenantID,
|
||||
Email: u.Email,
|
||||
ID: u.ID,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
Name: u.Name,
|
||||
LexicalName: u.LexicalName,
|
||||
Timezone: u.Timezone,
|
||||
Role: u.Role,
|
||||
Status: u.Status,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
}
|
||||
|
||||
// Map ProfileData if present
|
||||
if u.ProfileData != nil {
|
||||
userByEmail.Phone = u.ProfileData.Phone
|
||||
userByEmail.Country = u.ProfileData.Country
|
||||
userByEmail.Region = u.ProfileData.Region
|
||||
userByEmail.City = u.ProfileData.City
|
||||
userByEmail.PostalCode = u.ProfileData.PostalCode
|
||||
userByEmail.AddressLine1 = u.ProfileData.AddressLine1
|
||||
userByEmail.AddressLine2 = u.ProfileData.AddressLine2
|
||||
userByEmail.HasShippingAddress = u.ProfileData.HasShippingAddress
|
||||
userByEmail.ShippingName = u.ProfileData.ShippingName
|
||||
userByEmail.ShippingPhone = u.ProfileData.ShippingPhone
|
||||
userByEmail.ShippingCountry = u.ProfileData.ShippingCountry
|
||||
userByEmail.ShippingRegion = u.ProfileData.ShippingRegion
|
||||
userByEmail.ShippingCity = u.ProfileData.ShippingCity
|
||||
userByEmail.ShippingPostalCode = u.ProfileData.ShippingPostalCode
|
||||
userByEmail.ShippingAddressLine1 = u.ProfileData.ShippingAddressLine1
|
||||
userByEmail.ShippingAddressLine2 = u.ProfileData.ShippingAddressLine2
|
||||
userByEmail.ProfileTimezone = u.ProfileData.Timezone
|
||||
userByEmail.AgreeTermsOfService = u.ProfileData.AgreeTermsOfService
|
||||
userByEmail.AgreePromotions = u.ProfileData.AgreePromotions
|
||||
userByEmail.AgreeToTrackingAcrossThirdPartyAppsAndServices = u.ProfileData.AgreeToTrackingAcrossThirdPartyAppsAndServices
|
||||
}
|
||||
|
||||
// Map SecurityData if present
|
||||
if u.SecurityData != nil {
|
||||
userByEmail.PasswordHashAlgorithm = u.SecurityData.PasswordHashAlgorithm
|
||||
userByEmail.PasswordHash = u.SecurityData.PasswordHash
|
||||
userByEmail.WasEmailVerified = u.SecurityData.WasEmailVerified
|
||||
userByEmail.Code = u.SecurityData.Code
|
||||
userByEmail.CodeType = u.SecurityData.CodeType
|
||||
userByEmail.CodeExpiry = u.SecurityData.CodeExpiry
|
||||
userByEmail.OTPEnabled = u.SecurityData.OTPEnabled
|
||||
userByEmail.OTPVerified = u.SecurityData.OTPVerified
|
||||
userByEmail.OTPValidated = u.SecurityData.OTPValidated
|
||||
userByEmail.OTPSecret = u.SecurityData.OTPSecret
|
||||
userByEmail.OTPAuthURL = u.SecurityData.OTPAuthURL
|
||||
userByEmail.OTPBackupCodeHash = u.SecurityData.OTPBackupCodeHash
|
||||
userByEmail.OTPBackupCodeHashAlgorithm = u.SecurityData.OTPBackupCodeHashAlgorithm
|
||||
}
|
||||
|
||||
// Map Metadata if present
|
||||
if u.Metadata != nil {
|
||||
userByEmail.CreatedFromIPAddress = u.Metadata.CreatedFromIPAddress
|
||||
userByEmail.CreatedFromIPTimestamp = u.Metadata.CreatedFromIPTimestamp
|
||||
userByEmail.CreatedByUserID = u.Metadata.CreatedByUserID
|
||||
userByEmail.CreatedByName = u.Metadata.CreatedByName
|
||||
userByEmail.ModifiedFromIPAddress = u.Metadata.ModifiedFromIPAddress
|
||||
userByEmail.ModifiedFromIPTimestamp = u.Metadata.ModifiedFromIPTimestamp
|
||||
userByEmail.ModifiedByUserID = u.Metadata.ModifiedByUserID
|
||||
userByEmail.ModifiedAt = u.Metadata.ModifiedAt
|
||||
userByEmail.ModifiedByName = u.Metadata.ModifiedByName
|
||||
userByEmail.LastLoginAt = u.Metadata.LastLoginAt
|
||||
}
|
||||
|
||||
return userByEmail
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
|
||||
)
|
||||
|
||||
// UserByID represents the users_by_id table
|
||||
// Query pattern: Get user by ID
|
||||
// Primary key: (tenant_id, id) - composite partition key for multi-tenancy
|
||||
type UserByID struct {
|
||||
TenantID string `db:"tenant_id"` // Multi-tenant isolation
|
||||
ID string `db:"id"`
|
||||
Email string `db:"email"`
|
||||
FirstName string `db:"first_name"`
|
||||
LastName string `db:"last_name"`
|
||||
Name string `db:"name"`
|
||||
LexicalName string `db:"lexical_name"`
|
||||
Timezone string `db:"timezone"`
|
||||
Role int `db:"role"`
|
||||
Status int `db:"status"`
|
||||
|
||||
// Profile data fields (flattened)
|
||||
Phone string `db:"phone"`
|
||||
Country string `db:"country"`
|
||||
Region string `db:"region"`
|
||||
City string `db:"city"`
|
||||
PostalCode string `db:"postal_code"`
|
||||
AddressLine1 string `db:"address_line1"`
|
||||
AddressLine2 string `db:"address_line2"`
|
||||
HasShippingAddress bool `db:"has_shipping_address"`
|
||||
ShippingName string `db:"shipping_name"`
|
||||
ShippingPhone string `db:"shipping_phone"`
|
||||
ShippingCountry string `db:"shipping_country"`
|
||||
ShippingRegion string `db:"shipping_region"`
|
||||
ShippingCity string `db:"shipping_city"`
|
||||
ShippingPostalCode string `db:"shipping_postal_code"`
|
||||
ShippingAddressLine1 string `db:"shipping_address_line1"`
|
||||
ShippingAddressLine2 string `db:"shipping_address_line2"`
|
||||
ProfileTimezone string `db:"profile_timezone"`
|
||||
AgreeTermsOfService bool `db:"agree_terms_of_service"`
|
||||
AgreePromotions bool `db:"agree_promotions"`
|
||||
AgreeToTrackingAcrossThirdPartyAppsAndServices bool `db:"agree_to_tracking_across_third_party_apps_and_services"`
|
||||
|
||||
// Security data fields (flattened)
|
||||
PasswordHashAlgorithm string `db:"password_hash_algorithm"`
|
||||
PasswordHash string `db:"password_hash"`
|
||||
WasEmailVerified bool `db:"was_email_verified"`
|
||||
Code string `db:"code"`
|
||||
CodeType string `db:"code_type"`
|
||||
CodeExpiry time.Time `db:"code_expiry"`
|
||||
OTPEnabled bool `db:"otp_enabled"`
|
||||
OTPVerified bool `db:"otp_verified"`
|
||||
OTPValidated bool `db:"otp_validated"`
|
||||
OTPSecret string `db:"otp_secret"`
|
||||
OTPAuthURL string `db:"otp_auth_url"`
|
||||
OTPBackupCodeHash string `db:"otp_backup_code_hash"`
|
||||
OTPBackupCodeHashAlgorithm string `db:"otp_backup_code_hash_algorithm"`
|
||||
|
||||
// Metadata fields (flattened)
|
||||
// CWE-359: Encrypted IP addresses for GDPR compliance
|
||||
CreatedFromIPAddress string `db:"created_from_ip_address"` // Encrypted with go-ipcrypt
|
||||
CreatedFromIPTimestamp time.Time `db:"created_from_ip_timestamp"` // For 90-day expiration tracking
|
||||
CreatedByUserID string `db:"created_by_user_id"`
|
||||
CreatedByName string `db:"created_by_name"`
|
||||
ModifiedFromIPAddress string `db:"modified_from_ip_address"` // Encrypted with go-ipcrypt
|
||||
ModifiedFromIPTimestamp time.Time `db:"modified_from_ip_timestamp"` // For 90-day expiration tracking
|
||||
ModifiedByUserID string `db:"modified_by_user_id"`
|
||||
ModifiedAt time.Time `db:"modified_at"`
|
||||
ModifiedByName string `db:"modified_by_name"`
|
||||
LastLoginAt time.Time `db:"last_login_at"`
|
||||
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
// ToUser converts table model to domain entity
|
||||
func (u *UserByID) ToUser() *user.User {
|
||||
return &user.User{
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
Name: u.Name,
|
||||
LexicalName: u.LexicalName,
|
||||
Timezone: u.Timezone,
|
||||
Role: u.Role,
|
||||
Status: u.Status,
|
||||
|
||||
ProfileData: &user.UserProfileData{
|
||||
Phone: u.Phone,
|
||||
Country: u.Country,
|
||||
Region: u.Region,
|
||||
City: u.City,
|
||||
PostalCode: u.PostalCode,
|
||||
AddressLine1: u.AddressLine1,
|
||||
AddressLine2: u.AddressLine2,
|
||||
HasShippingAddress: u.HasShippingAddress,
|
||||
ShippingName: u.ShippingName,
|
||||
ShippingPhone: u.ShippingPhone,
|
||||
ShippingCountry: u.ShippingCountry,
|
||||
ShippingRegion: u.ShippingRegion,
|
||||
ShippingCity: u.ShippingCity,
|
||||
ShippingPostalCode: u.ShippingPostalCode,
|
||||
ShippingAddressLine1: u.ShippingAddressLine1,
|
||||
ShippingAddressLine2: u.ShippingAddressLine2,
|
||||
Timezone: u.ProfileTimezone,
|
||||
AgreeTermsOfService: u.AgreeTermsOfService,
|
||||
AgreePromotions: u.AgreePromotions,
|
||||
AgreeToTrackingAcrossThirdPartyAppsAndServices: u.AgreeToTrackingAcrossThirdPartyAppsAndServices,
|
||||
},
|
||||
|
||||
SecurityData: &user.UserSecurityData{
|
||||
PasswordHashAlgorithm: u.PasswordHashAlgorithm,
|
||||
PasswordHash: u.PasswordHash,
|
||||
WasEmailVerified: u.WasEmailVerified,
|
||||
Code: u.Code,
|
||||
CodeType: u.CodeType,
|
||||
CodeExpiry: u.CodeExpiry,
|
||||
OTPEnabled: u.OTPEnabled,
|
||||
OTPVerified: u.OTPVerified,
|
||||
OTPValidated: u.OTPValidated,
|
||||
OTPSecret: u.OTPSecret,
|
||||
OTPAuthURL: u.OTPAuthURL,
|
||||
OTPBackupCodeHash: u.OTPBackupCodeHash,
|
||||
OTPBackupCodeHashAlgorithm: u.OTPBackupCodeHashAlgorithm,
|
||||
},
|
||||
|
||||
Metadata: &user.UserMetadata{
|
||||
CreatedFromIPAddress: u.CreatedFromIPAddress,
|
||||
CreatedFromIPTimestamp: u.CreatedFromIPTimestamp,
|
||||
CreatedByUserID: u.CreatedByUserID,
|
||||
CreatedAt: u.CreatedAt,
|
||||
CreatedByName: u.CreatedByName,
|
||||
ModifiedFromIPAddress: u.ModifiedFromIPAddress,
|
||||
ModifiedFromIPTimestamp: u.ModifiedFromIPTimestamp,
|
||||
ModifiedByUserID: u.ModifiedByUserID,
|
||||
ModifiedAt: u.ModifiedAt,
|
||||
ModifiedByName: u.ModifiedByName,
|
||||
LastLoginAt: u.LastLoginAt,
|
||||
},
|
||||
|
||||
TenantID: u.TenantID,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// FromUser converts domain entity to table model
|
||||
func FromUser(tenantID string, u *user.User) *UserByID {
|
||||
userByID := &UserByID{
|
||||
TenantID: tenantID,
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
Name: u.Name,
|
||||
LexicalName: u.LexicalName,
|
||||
Timezone: u.Timezone,
|
||||
Role: u.Role,
|
||||
Status: u.Status,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
}
|
||||
|
||||
// Map ProfileData if present
|
||||
if u.ProfileData != nil {
|
||||
userByID.Phone = u.ProfileData.Phone
|
||||
userByID.Country = u.ProfileData.Country
|
||||
userByID.Region = u.ProfileData.Region
|
||||
userByID.City = u.ProfileData.City
|
||||
userByID.PostalCode = u.ProfileData.PostalCode
|
||||
userByID.AddressLine1 = u.ProfileData.AddressLine1
|
||||
userByID.AddressLine2 = u.ProfileData.AddressLine2
|
||||
userByID.HasShippingAddress = u.ProfileData.HasShippingAddress
|
||||
userByID.ShippingName = u.ProfileData.ShippingName
|
||||
userByID.ShippingPhone = u.ProfileData.ShippingPhone
|
||||
userByID.ShippingCountry = u.ProfileData.ShippingCountry
|
||||
userByID.ShippingRegion = u.ProfileData.ShippingRegion
|
||||
userByID.ShippingCity = u.ProfileData.ShippingCity
|
||||
userByID.ShippingPostalCode = u.ProfileData.ShippingPostalCode
|
||||
userByID.ShippingAddressLine1 = u.ProfileData.ShippingAddressLine1
|
||||
userByID.ShippingAddressLine2 = u.ProfileData.ShippingAddressLine2
|
||||
userByID.ProfileTimezone = u.ProfileData.Timezone
|
||||
userByID.AgreeTermsOfService = u.ProfileData.AgreeTermsOfService
|
||||
userByID.AgreePromotions = u.ProfileData.AgreePromotions
|
||||
userByID.AgreeToTrackingAcrossThirdPartyAppsAndServices = u.ProfileData.AgreeToTrackingAcrossThirdPartyAppsAndServices
|
||||
}
|
||||
|
||||
// Map SecurityData if present
|
||||
if u.SecurityData != nil {
|
||||
userByID.PasswordHashAlgorithm = u.SecurityData.PasswordHashAlgorithm
|
||||
userByID.PasswordHash = u.SecurityData.PasswordHash
|
||||
userByID.WasEmailVerified = u.SecurityData.WasEmailVerified
|
||||
userByID.Code = u.SecurityData.Code
|
||||
userByID.CodeType = u.SecurityData.CodeType
|
||||
userByID.CodeExpiry = u.SecurityData.CodeExpiry
|
||||
userByID.OTPEnabled = u.SecurityData.OTPEnabled
|
||||
userByID.OTPVerified = u.SecurityData.OTPVerified
|
||||
userByID.OTPValidated = u.SecurityData.OTPValidated
|
||||
userByID.OTPSecret = u.SecurityData.OTPSecret
|
||||
userByID.OTPAuthURL = u.SecurityData.OTPAuthURL
|
||||
userByID.OTPBackupCodeHash = u.SecurityData.OTPBackupCodeHash
|
||||
userByID.OTPBackupCodeHashAlgorithm = u.SecurityData.OTPBackupCodeHashAlgorithm
|
||||
}
|
||||
|
||||
// Map Metadata if present
|
||||
if u.Metadata != nil {
|
||||
userByID.CreatedFromIPAddress = u.Metadata.CreatedFromIPAddress
|
||||
userByID.CreatedFromIPTimestamp = u.Metadata.CreatedFromIPTimestamp
|
||||
userByID.CreatedByUserID = u.Metadata.CreatedByUserID
|
||||
userByID.CreatedByName = u.Metadata.CreatedByName
|
||||
userByID.ModifiedFromIPAddress = u.Metadata.ModifiedFromIPAddress
|
||||
userByID.ModifiedFromIPTimestamp = u.Metadata.ModifiedFromIPTimestamp
|
||||
userByID.ModifiedByUserID = u.Metadata.ModifiedByUserID
|
||||
userByID.ModifiedAt = u.Metadata.ModifiedAt
|
||||
userByID.ModifiedByName = u.Metadata.ModifiedByName
|
||||
userByID.LastLoginAt = u.Metadata.LastLoginAt
|
||||
}
|
||||
|
||||
return userByID
|
||||
}
|
||||
53
cloud/maplepress-backend/internal/repository/user/update.go
Normal file
53
cloud/maplepress-backend/internal/repository/user/update.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"go.uber.org/zap"
|
||||
|
||||
domainuser "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/repository/user/models"
|
||||
)
|
||||
|
||||
// Update updates an existing user in all tables using batched writes
|
||||
func (r *repository) Update(ctx context.Context, tenantID string, u *domainuser.User) error {
|
||||
r.logger.Info("updating user",
|
||||
zap.String("tenant_id", tenantID),
|
||||
zap.String("id", u.ID))
|
||||
|
||||
// Convert domain entity to table models
|
||||
userByID := models.FromUser(tenantID, u)
|
||||
userByEmail := models.FromUserByEmail(tenantID, u)
|
||||
userByDate := models.FromUserByDate(tenantID, u)
|
||||
|
||||
// Use batched writes to maintain consistency across all tables
|
||||
batch := r.session.NewBatch(gocql.LoggedBatch)
|
||||
|
||||
// Update users_by_id table
|
||||
batch.Query(`UPDATE users_by_id
|
||||
SET name = ?, updated_at = ?
|
||||
WHERE tenant_id = ? AND id = ?`,
|
||||
userByID.Name, userByID.UpdatedAt, userByID.TenantID, userByID.ID)
|
||||
|
||||
// Update users_by_email table
|
||||
batch.Query(`UPDATE users_by_email
|
||||
SET name = ?, updated_at = ?
|
||||
WHERE tenant_id = ? AND email = ?`,
|
||||
userByEmail.Name, userByEmail.UpdatedAt, userByEmail.TenantID, userByEmail.Email)
|
||||
|
||||
// Update users_by_date table
|
||||
batch.Query(`UPDATE users_by_date
|
||||
SET name = ?, updated_at = ?
|
||||
WHERE tenant_id = ? AND created_date = ? AND id = ?`,
|
||||
userByDate.Name, userByDate.UpdatedAt, userByDate.TenantID, userByDate.CreatedDate, userByDate.ID)
|
||||
|
||||
// Execute batch atomically
|
||||
if err := r.session.ExecuteBatch(batch); err != nil {
|
||||
r.logger.Error("failed to update user", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("user updated successfully", zap.String("user_id", u.ID))
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue