Add cursor rules and initial configuration for Tender Management Go Backend

This commit is contained in:
n.nakhostin
2025-08-02 10:43:47 +03:30
parent ad9db7bcce
commit 5a1ceb0bd6
35 changed files with 4123 additions and 4550 deletions
@@ -0,0 +1,37 @@
package aggregate
import (
"tm/internal/customer/domain/entity"
"github.com/google/uuid"
)
type Customer struct {
ID uuid.UUID `json:"_id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Mobile string `json:"mobile"`
Email string `json:"email"`
IsActive bool `json:"is_active"`
IsVerified bool `json:"is_verified"`
}
func NewCustomer(c entity.Customer) Customer {
return Customer{
ID: c.ID,
FirstName: c.FirstName,
LastName: c.LastName,
Mobile: c.Mobile,
Email: c.Email,
IsActive: c.IsActive,
IsVerified: c.IsVerified,
}
}
// AuthResponse defines authentication response (kept for backward compatibility)
type AuthorizationResponse struct {
Customer Customer `json:"customer"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int64 `json:"expires_in"`
}
+331
View File
@@ -0,0 +1,331 @@
package customer
import (
"context"
"errors"
"time"
"tm/internal/customer/domain/entity"
"tm/pkg/logger"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// customerRepository implements the Repository interface
type customerRepository struct {
collection *mongo.Collection
logger logger.Logger
}
// NewCustomerRepository creates a new customer repository
func NewCustomerRepository(db *mongo.Database, logger logger.Logger) Repository {
collection := db.Collection("customers")
// Create indexes
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Email index (unique)
emailIndex := mongo.IndexModel{
Keys: bson.D{{Key: "email", Value: 1}},
Options: options.Index().SetUnique(true),
}
// Company ID index
companyIndex := mongo.IndexModel{
Keys: bson.D{{Key: "company_id", Value: 1}},
}
// Active status index
activeIndex := mongo.IndexModel{
Keys: bson.D{{Key: "is_active", Value: 1}},
}
// Created at index
createdAtIndex := mongo.IndexModel{
Keys: bson.D{{Key: "created_at", Value: -1}},
}
_, err := collection.Indexes().CreateMany(ctx, []mongo.IndexModel{
emailIndex,
companyIndex,
activeIndex,
createdAtIndex,
})
if err != nil {
logger.Warn("Failed to create customer indexes", map[string]interface{}{
"error": err.Error(),
})
}
return &customerRepository{
collection: collection,
logger: logger,
}
}
// Create creates a new customer
func (r *customerRepository) Create(ctx context.Context, customer *entity.Customer) error {
// Set created/updated timestamps
now := time.Now()
customer.CreatedAt = now
customer.UpdatedAt = now
// Insert customer
_, err := r.collection.InsertOne(ctx, customer)
if err != nil {
if mongo.IsDuplicateKeyError(err) {
return errors.New("customer already exists")
}
r.logger.Error("Failed to create customer", map[string]interface{}{
"error": err.Error(),
"email": customer.Email,
"customer_id": customer.ID.String(),
})
return err
}
r.logger.Info("Customer created successfully", map[string]interface{}{
"customer_id": customer.ID.String(),
"email": customer.Email,
})
return nil
}
// GetByID retrieves a customer by ID
func (r *customerRepository) GetByID(ctx context.Context, id uuid.UUID) (*entity.Customer, error) {
var customer entity.Customer
filter := bson.M{"_id": id}
err := r.collection.FindOne(ctx, filter).Decode(&customer)
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, errors.New("customer not found")
}
r.logger.Error("Failed to get customer by ID", map[string]interface{}{
"error": err.Error(),
"customer_id": id.String(),
})
return nil, err
}
return &customer, nil
}
// GetByEmail retrieves a customer by email
func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*entity.Customer, error) {
var customer entity.Customer
filter := bson.M{"email": email}
err := r.collection.FindOne(ctx, filter).Decode(&customer)
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, errors.New("customer not found")
}
r.logger.Error("Failed to get customer by email", map[string]interface{}{
"error": err.Error(),
"email": email,
})
return nil, err
}
return &customer, nil
}
// Update updates a customer
func (r *customerRepository) Update(ctx context.Context, customer *entity.Customer) error {
// Set updated timestamp
customer.UpdatedAt = time.Now()
filter := bson.M{"_id": customer.ID}
update := bson.M{"$set": customer}
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil {
r.logger.Error("Failed to update customer", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID.String(),
})
return err
}
if result.MatchedCount == 0 {
return errors.New("customer not found")
}
r.logger.Info("Customer updated successfully", map[string]interface{}{
"customer_id": customer.ID.String(),
"email": customer.Email,
})
return nil
}
// Delete deletes a customer (soft delete by setting is_active to false)
func (r *customerRepository) Delete(ctx context.Context, id uuid.UUID) error {
filter := bson.M{"_id": id}
update := bson.M{
"$set": bson.M{
"is_active": false,
"updated_at": time.Now(),
},
}
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil {
r.logger.Error("Failed to delete customer", map[string]interface{}{
"error": err.Error(),
"customer_id": id.String(),
})
return err
}
if result.MatchedCount == 0 {
return errors.New("customer not found")
}
r.logger.Info("Customer deleted successfully", map[string]interface{}{
"customer_id": id.String(),
})
return nil
}
// List retrieves customers with pagination
func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*entity.Customer, error) {
var customers []*entity.Customer
// Build options
opts := options.Find()
opts.SetLimit(int64(limit))
opts.SetSkip(int64(offset))
opts.SetSort(bson.D{{Key: "created_at", Value: -1}}) // Sort by created_at desc
// Only active customers by default
filter := bson.M{"is_active": true}
cursor, err := r.collection.Find(ctx, filter, opts)
if err != nil {
r.logger.Error("Failed to list customers", map[string]interface{}{
"error": err.Error(),
"limit": limit,
"offset": offset,
})
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &customers); err != nil {
r.logger.Error("Failed to decode customers", map[string]interface{}{
"error": err.Error(),
})
return nil, err
}
return customers, nil
}
// GetByCompanyID retrieves customers by company ID with pagination
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*entity.Customer, error) {
var customers []*entity.Customer
// Build options
opts := options.Find()
opts.SetLimit(int64(limit))
opts.SetSkip(int64(offset))
opts.SetSort(bson.D{{Key: "created_at", Value: -1}}) // Sort by created_at desc
// Filter by company ID and active status
filter := bson.M{
"company_id": companyID,
"is_active": true,
}
cursor, err := r.collection.Find(ctx, filter, opts)
if err != nil {
r.logger.Error("Failed to get customers by company ID", map[string]interface{}{
"error": err.Error(),
"company_id": companyID.String(),
"limit": limit,
"offset": offset,
})
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &customers); err != nil {
r.logger.Error("Failed to decode customers by company", map[string]interface{}{
"error": err.Error(),
"company_id": companyID.String(),
})
return nil, err
}
return customers, nil
}
// AddDeviceToken adds a device token for push notifications
func (r *customerRepository) AddDeviceToken(ctx context.Context, id uuid.UUID, token string) error {
filter := bson.M{"_id": id}
update := bson.M{
"$addToSet": bson.M{"device_tokens": token}, // Use $addToSet to avoid duplicates
"$set": bson.M{"updated_at": time.Now()},
}
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil {
r.logger.Error("Failed to add device token", map[string]interface{}{
"error": err.Error(),
"customer_id": id.String(),
"token": token,
})
return err
}
if result.MatchedCount == 0 {
return errors.New("customer not found")
}
r.logger.Info("Device token added successfully", map[string]interface{}{
"customer_id": id.String(),
"token": token,
})
return nil
}
// RemoveDeviceToken removes a device token
func (r *customerRepository) RemoveDeviceToken(ctx context.Context, id uuid.UUID, token string) error {
filter := bson.M{"_id": id}
update := bson.M{
"$pull": bson.M{"device_tokens": token}, // Use $pull to remove the token
"$set": bson.M{"updated_at": time.Now()},
}
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil {
r.logger.Error("Failed to remove device token", map[string]interface{}{
"error": err.Error(),
"customer_id": id.String(),
"token": token,
})
return err
}
if result.MatchedCount == 0 {
return errors.New("customer not found")
}
r.logger.Info("Device token removed successfully", map[string]interface{}{
"customer_id": id.String(),
"token": token,
})
return nil
}
+24
View File
@@ -0,0 +1,24 @@
package entity
import (
"time"
"github.com/google/uuid"
)
// Customer represents a mobile app user (customer)
type Customer struct {
ID uuid.UUID `bson:"_id"`
Email string `bson:"email"`
Password string `bson:"password"` // Never serialize password
FirstName string `bson:"first_name"`
LastName string `bson:"last_name"`
Mobile string `bson:"mobile"`
CompanyID uuid.UUID `bson:"company_id"`
IsActive bool `bson:"is_active"`
IsVerified bool `bson:"is_verified"`
DeviceTokens []string `bson:"device_tokens"` // For push notifications
LastLoginAt *time.Time `bson:"last_login_at,omitempty"`
CreatedAt time.Time `bson:"created_at"`
UpdatedAt time.Time `bson:"updated_at"`
}
+21
View File
@@ -0,0 +1,21 @@
package customer
import (
"context"
"tm/internal/customer/domain/entity"
"github.com/google/uuid"
)
// CustomerRepository defines methods for customer (mobile user) data access
type Repository interface {
Create(ctx context.Context, customer *entity.Customer) error
GetByID(ctx context.Context, id uuid.UUID) (*entity.Customer, error)
GetByEmail(ctx context.Context, email string) (*entity.Customer, error)
Update(ctx context.Context, customer *entity.Customer) error
Delete(ctx context.Context, id uuid.UUID) error
List(ctx context.Context, limit, offset int) ([]*entity.Customer, error)
GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*entity.Customer, error)
AddDeviceToken(ctx context.Context, id uuid.UUID, token string) error
RemoveDeviceToken(ctx context.Context, id uuid.UUID, token string) error
}