From 245f42156c8ec1b184c68f89e5c6a8e68ef7b54b Mon Sep 17 00:00:00 2001 From: Rajeev Singh Date: Sun, 10 Mar 2024 19:02:34 +0530 Subject: [PATCH 1/5] added lead service --- inject.go | 2 + main.go | 1 + service/lead_service.go | 261 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 service/lead_service.go diff --git a/inject.go b/inject.go index 1ea9f06..9e26f3e 100644 --- a/inject.go +++ b/inject.go @@ -18,6 +18,7 @@ type Inject struct { LoginVerifiedService *service.LoginVerifiedService ProfileService *service.ProfileService ProfileMasterService *service.ProfileMasterService + LeadService *service.LeadService UnaryInterceptors []grpc.UnaryServerInterceptor StreamInterceptors []grpc.StreamServerInterceptor } @@ -35,6 +36,7 @@ func NewInject() *Inject { inj.LoginVerifiedService = service.NewLoginVerifiedService(inj.AuthDb) inj.ProfileService = service.NewProfileService(inj.AuthDb, inj.CloudFns) inj.ProfileMasterService = service.NewProfileMasterService(inj.AuthDb) + inj.LeadService = service.NewLeadService(inj.AuthDb) return inj } diff --git a/main.go b/main.go index 58260b9..8b9237a 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ func main() { pb.RegisterLoginVerifiedServer(bootServer.GrpcServer, inject.LoginVerifiedService) pb.RegisterProfileServer(bootServer.GrpcServer, inject.ProfileService) pb.RegisterProfileMasterServer(bootServer.GrpcServer, inject.ProfileMasterService) + pb.RegisterLeadServiceServer(bootServer.GrpcServer, inject.LeadService) bootServer.Start(grpcPort, webPort) } diff --git a/service/lead_service.go b/service/lead_service.go new file mode 100644 index 0000000..af47465 --- /dev/null +++ b/service/lead_service.go @@ -0,0 +1,261 @@ +package service + +import ( + "context" + + "github.com/Kotlang/authGo/db" + pb "github.com/Kotlang/authGo/generated" + "github.com/Kotlang/authGo/models" + "github.com/SaiNageswarS/go-api-boot/auth" + "github.com/SaiNageswarS/go-api-boot/logger" + "github.com/jinzhu/copier" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type LeadServiceInterface interface { + pb.LeadServiceServer + db.AuthDbInterface +} + +type LeadService struct { + pb.UnimplementedLeadServiceServer + db db.AuthDbInterface +} + +func NewLeadService(db db.AuthDbInterface) *LeadService { + return &LeadService{db: db} +} + +// Admin only API +func (s *LeadService) CreateLead(ctx context.Context, req *pb.CreateOrUpdateLeadRequest) (*pb.LeadProto, error) { + + userId, tenant := auth.GetUserIdAndTenant(ctx) + + // check if user is admin + if !s.db.Login(tenant).IsAdmin(userId) { + logger.Error("User is not admin", zap.String("userId", userId)) + return nil, status.Error(codes.PermissionDenied, "User is not admin") + } + // get the lead model from the request + lead := getLeadModel(req) + + // save to db + err := <-s.db.Lead(tenant).Save(lead) + + if err != nil { + logger.Error("Error saving lead", zap.Error(err)) + return nil, err + } + + // return the lead + leadProto := getLeadProto(lead) + + return leadProto, nil +} + +// Admin only API +func (s *LeadService) GetLeadById(ctx context.Context, req *pb.LeadIdRequest) (*pb.LeadProto, error) { + userId, tenant := auth.GetUserIdAndTenant(ctx) + + // check if user is admin + if !s.db.Login(tenant).IsAdmin(userId) { + logger.Error("User is not admin", zap.String("userId", userId)) + return nil, status.Error(codes.PermissionDenied, "User is not admin") + } + + // get the lead from db + leadResChan, errChan := s.db.Lead(tenant).FindOneById(req.LeadId) + select { + case lead := <-leadResChan: + leadProto := getLeadProto(lead) + return leadProto, nil + case err := <-errChan: + logger.Error("Error getting lead", zap.Error(err)) + return nil, err + } +} + +// Admin only API +func (s *LeadService) BulkGetLeadsById(ctx context.Context, req *pb.BulkIdRequest) (*pb.LeadListResponse, error) { + userId, tenant := auth.GetUserIdAndTenant(ctx) + + // check if user is admin + if !s.db.Login(tenant).IsAdmin(userId) { + logger.Error("User is not admin", zap.String("userId", userId)) + return nil, status.Error(codes.PermissionDenied, "User is not admin") + } + + // get the leads from db + leadResChan, errChan := s.db.Lead(tenant).FindByIds(req.LeadIds) + select { + case leads := <-leadResChan: + leadProtos := make([]*pb.LeadProto, len(leads)) + for i, lead := range leads { + leadProtos[i] = getLeadProto(&lead) + } + return &pb.LeadListResponse{Leads: leadProtos}, nil + case err := <-errChan: + logger.Error("Error getting leads", zap.Error(err)) + return nil, err + + } +} + +// Admin only API +func (s *LeadService) UpdateLead(ctx context.Context, req *pb.CreateOrUpdateLeadRequest) (*pb.LeadProto, error) { + userId, tenant := auth.GetUserIdAndTenant(ctx) + + // check if user is admin + if !s.db.Login(tenant).IsAdmin(userId) { + logger.Error("User is not admin", zap.String("userId", userId)) + return nil, status.Error(codes.PermissionDenied, "User is not admin") + } + + // get the lead model from the request + lead := getLeadModel(req) + + // save to db + err := <-s.db.Lead(tenant).Save(lead) + + if err != nil { + logger.Error("Error saving lead", zap.Error(err)) + return nil, err + } + + // return the lead + leadProto := getLeadProto(lead) + + return leadProto, nil +} + +// Admin only API +func (s *LeadService) DeleteLead(ctx context.Context, req *pb.LeadIdRequest) (*pb.StatusResponse, error) { + userId, tenant := auth.GetUserIdAndTenant(ctx) + + // check if user is admin + if !s.db.Login(tenant).IsAdmin(userId) { + logger.Error("User is not admin", zap.String("userId", userId)) + return nil, status.Error(codes.PermissionDenied, "User is not admin") + } + + // delete the lead + err := <-s.db.Lead(tenant).DeleteById(req.LeadId) + if err != nil { + logger.Error("Error deleting lead", zap.Error(err)) + return nil, err + } + + return &pb.StatusResponse{ + Status: "Success", + }, nil +} + +// Admin only API +func (s *LeadService) FetchLeads(ctx context.Context, req *pb.FetchLeadsRequest) (*pb.LeadListResponse, error) { + userId, tenant := auth.GetUserIdAndTenant(ctx) + + // check if user is admin + if !s.db.Login(tenant).IsAdmin(userId) { + logger.Error("User is not admin", zap.String("userId", userId)) + return nil, status.Error(codes.PermissionDenied, "User is not admin") + } + + // get the leads from db + leads, totalCount := s.db.Lead(tenant).GetLeads(req.LeadFilters, int64(req.PageNumber), int64(req.PageSize)) + + leadProtos := make([]*pb.LeadProto, len(leads)) + for i, lead := range leads { + leadProtos[i] = getLeadProto(&lead) + } + + return &pb.LeadListResponse{ + Leads: leadProtos, + TotalLeads: int64(totalCount), + }, nil + +} + +func getLeadModel(req *pb.CreateOrUpdateLeadRequest) *models.LeadModel { + + // copying the request to lead model + lead := &models.LeadModel{} + copier.CopyWithOption(lead, req, copier.Option{IgnoreEmpty: true, DeepCopy: true}) + + // Copy the operator type + if req.OperatorType != pb.OperatorType_UNSPECIFIED_OPERATOR { + value, ok := pb.OperatorType_name[int32(req.OperatorType)] + if !ok { + lead.OperatorType = pb.OperatorType_name[int32(pb.OperatorType_UNSPECIFIED_OPERATOR)] + } + lead.OperatorType = value + } + + // Copy the lead channel + if req.Channel != pb.LeadChannel_UNSPECIFIED_CHANNEL { + value, ok := pb.LeadChannel_name[int32(req.Channel)] + if !ok { + lead.Channel = pb.LeadChannel_name[int32(pb.LeadChannel_UNSPECIFIED_CHANNEL)] + } + lead.Channel = value + } + + // Copy the farming type + if req.FarmingType != pb.FarmingType_UnspecifiedFarming { + value, ok := pb.FarmingType_name[int32(req.FarmingType)] + if !ok { + value = pb.FarmingType_name[int32(pb.FarmingType_UnspecifiedFarming)] + } + lead.FarmingType = value + } + + // Copy the land size + if req.LandSizeInAcres != pb.LandSizeInAcres_UnspecifiedLandSize { + value, ok := pb.LandSizeInAcres_name[int32(req.LandSizeInAcres)] + if !ok { + value = pb.LandSizeInAcres_name[int32(pb.LandSizeInAcres_UnspecifiedLandSize)] + } + lead.LandSizeInAcres = value + } + + // Todo: Copy the lead status + return lead +} + +func getLeadProto(lead *models.LeadModel) *pb.LeadProto { + leadProto := &pb.LeadProto{} + copier.CopyWithOption(leadProto, lead, copier.Option{IgnoreEmpty: true, DeepCopy: true}) + + // Copy the operator type + value, ok := pb.OperatorType_value[lead.OperatorType] + if !ok { + leadProto.OperatorType = pb.OperatorType_UNSPECIFIED_OPERATOR + } + leadProto.OperatorType = pb.OperatorType(value) + + // Copy the lead channel + value, ok = pb.LeadChannel_value[lead.Channel] + if !ok { + leadProto.Channel = pb.LeadChannel_UNSPECIFIED_CHANNEL + } + leadProto.Channel = pb.LeadChannel(value) + + // Copy the farming type + value, ok = pb.FarmingType_value[lead.FarmingType] + if !ok { + leadProto.FarmingType = pb.FarmingType_UnspecifiedFarming + } + leadProto.FarmingType = pb.FarmingType(value) + + // Copy the certification details + value, ok = pb.LandSizeInAcres_value[lead.LandSizeInAcres] + if !ok { + leadProto.LandSizeInAcres = pb.LandSizeInAcres_UnspecifiedLandSize + } + leadProto.LandSizeInAcres = pb.LandSizeInAcres(value) + + // TODO: Copy the lead status + + return leadProto +} From eb664f318331a7aa49a8e9ab337d0e9e09f00b5d Mon Sep 17 00:00:00 2001 From: Rajeev Singh Date: Sun, 10 Mar 2024 19:03:41 +0530 Subject: [PATCH 2/5] added lead models --- models/lead_model.go | 30 ++++++++++++++++++++++++++++++ models/profile_model.go | 31 ++++++++++++++++--------------- 2 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 models/lead_model.go diff --git a/models/lead_model.go b/models/lead_model.go new file mode 100644 index 0000000..754c0be --- /dev/null +++ b/models/lead_model.go @@ -0,0 +1,30 @@ +package models + +import "github.com/google/uuid" + +type LeadModel struct { + LeadId string `bson:"_id"` + Name string `bson:"name"` + PhoneNumber string `bson:"phoneNumber"` + OperatorType string `bson:"operatorType"` + Channel string `bson:"channel"` + Source string `bson:"source"` + Addresses []Addresses `bson:"addresses"` + LandSizeInAcres string `bson:"landSizeInAcres"` + FarmingType string `bson:"farmingType"` + CertificationDetails CertificateModel `bson:"certificationDetails"` + Crops []string `bson:"crops"` + MainProfession string `bson:"mainProfession"` + OrganizationName string `bson:"organizationName"` + SideProfession string `bson:"sideProfession"` + UserInterviewNotes string `bson:"userInterviewNotes"` + Education string `bson:"education"` +} + +func (m *LeadModel) Id() string { + if m.LeadId == "" { + m.LeadId = uuid.New().String() + } + + return m.LeadId +} diff --git a/models/profile_model.go b/models/profile_model.go index 903240d..8a65737 100644 --- a/models/profile_model.go +++ b/models/profile_model.go @@ -12,6 +12,7 @@ type Location struct { } type Addresses struct { + Type string `bson:"type" json:"type"` Address string `bson:"address" json:"address"` City string `bson:"city" json:"city"` State string `bson:"state" json:"state"` @@ -25,21 +26,21 @@ type DeletionInfo struct { } type ProfileModel struct { - UserId string `bson:"_id" json:"userId"` - Name string `bson:"name,omitempty" json:"name"` - PhotoUrl string `bson:"photoUrl" json:"photoUrl"` - Addresses map[string]Addresses `bson:"addresses" json:"addresses"` - Location Location `bson:"location" json:"location"` - FarmingType string `bson:"farmingType" json:"farmingType"` - Bio string `bson:"bio" json:"bio"` - Crops []string `bson:"crops" json:"crops"` - YearsSinceOrganicFarming int `bson:"yearsSinceOrganicFarming" json:"yearsSinceOrganicFarming"` - Gender string `bson:"gender" json:"gender" copier:"-"` - IsVerified bool `bson:"isVerified" json:"isVerified"` - PreferredLanguage string `bson:"preferredLanguage" json:"preferredLanguage"` - CertificationDetails CertificateModel `bson:"certificationDetails" json:"certificationDetails"` - CreatedOn int64 `bson:"createdOn,omitempty" json:"createdOn"` - LandSizeInAcres string `bson:"landSizeInAcres" json:"landSizeInAcres"` + UserId string `bson:"_id" json:"userId"` + Name string `bson:"name,omitempty" json:"name"` + PhotoUrl string `bson:"photoUrl" json:"photoUrl"` + Addresses []Addresses `bson:"addresses" json:"addresses"` + Location Location `bson:"location" json:"location"` + FarmingType string `bson:"farmingType" json:"farmingType"` + Bio string `bson:"bio" json:"bio"` + Crops []string `bson:"crops" json:"crops"` + YearsSinceOrganicFarming int `bson:"yearsSinceOrganicFarming" json:"yearsSinceOrganicFarming"` + Gender string `bson:"gender" json:"gender" copier:"-"` + IsVerified bool `bson:"isVerified" json:"isVerified"` + PreferredLanguage string `bson:"preferredLanguage" json:"preferredLanguage"` + CertificationDetails CertificateModel `bson:"certificationDetails" json:"certificationDetails"` + CreatedOn int64 `bson:"createdOn,omitempty" json:"createdOn"` + LandSizeInAcres string `bson:"landSizeInAcres" json:"landSizeInAcres"` } func (m *ProfileModel) Id() string { From b3b0216f7e3ab0b771e68035c73c1e45c5fc4ab1 Mon Sep 17 00:00:00 2001 From: Rajeev Singh Date: Sun, 10 Mar 2024 19:04:49 +0530 Subject: [PATCH 3/5] added lead --- db/auth_db.go | 10 +++ db/lead_repository.go | 147 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 db/lead_repository.go diff --git a/db/auth_db.go b/db/auth_db.go index eb93c29..9ecc830 100644 --- a/db/auth_db.go +++ b/db/auth_db.go @@ -10,6 +10,7 @@ type AuthDbInterface interface { Profile(tenant string) ProfileRepositoryInterface Tenant() TenantRepositoryInterface ProfileMaster(tenant string) ProfileMasterRepositoryInterface + Lead(tenant string) LeadRepositoryInterface } type AuthDb struct{} @@ -47,3 +48,12 @@ func (a *AuthDb) ProfileMaster(tenant string) ProfileMasterRepositoryInterface { return &ProfileMasterRepository{baseRepo} } + +func (a *AuthDb) Lead(tenant string) LeadRepositoryInterface { + baseRepo := odm.UnimplementedBootRepository[models.LeadModel]{ + Database: tenant + "_auth", + CollectionName: "lead", + } + + return &LeadRepository{baseRepo} +} diff --git a/db/lead_repository.go b/db/lead_repository.go new file mode 100644 index 0000000..0ec46ed --- /dev/null +++ b/db/lead_repository.go @@ -0,0 +1,147 @@ +package db + +import ( + pb "github.com/Kotlang/authGo/generated" + "github.com/Kotlang/authGo/models" + "github.com/SaiNageswarS/go-api-boot/logger" + "github.com/SaiNageswarS/go-api-boot/odm" + "go.mongodb.org/mongo-driver/bson" + "go.uber.org/zap" +) + +type LeadRepositoryInterface interface { + odm.BootRepository[models.LeadModel] + FindByIds(ids []string) (chan []models.LeadModel, chan error) + GetLeads(leadFilters *pb.LeadFilters, PageSize, PageNumber int64) (leads []models.LeadModel, totalCount int) +} + +type LeadRepository struct { + odm.UnimplementedBootRepository[models.LeadModel] +} + +func (l *LeadRepository) FindByIds(ids []string) (chan []models.LeadModel, chan error) { + filter := bson.M{ + "_id": bson.M{ + "$in": ids, + }, + } + return l.Find(filter, nil, 0, 0) +} + +func (l *LeadRepository) GetLeads(leadFilters *pb.LeadFilters, PageSize, PageNumber int64) (leads []models.LeadModel, totalCount int) { + + // get the filter + filter := getLeadFilter(leadFilters) + + // get the leads and total count + skip := PageNumber * PageSize + resultChan, errChan := l.Find(filter, nil, PageSize, skip) + totalCountResChan, countErrChan := l.CountDocuments(filter) + totalCount = 0 + + select { + case count := <-totalCountResChan: + totalCount = int(count) + case err := <-countErrChan: + logger.Error("Error fetching lead count", zap.Error(err)) + } + + select { + case res := <-resultChan: + leads = res + case err := <-errChan: + logger.Error("Error fetching leads", zap.Error(err)) + } + + return leads, totalCount +} + +func getLeadFilter(leadFilters *pb.LeadFilters) bson.M { + + if leadFilters == nil { + return bson.M{} + } + + filter := bson.M{} + + // if operator type is not unspecified then add it to filter + if leadFilters.OperatorType != pb.OperatorType_UNSPECIFIED_OPERATOR { + filter["operatorType"] = leadFilters.OperatorType.String() + } + + // if channel is not unspecified then add it to filter + if leadFilters.Channel != pb.LeadChannel_UNSPECIFIED_CHANNEL { + filter["channel"] = leadFilters.Channel.String() + } + + if leadFilters.Source != "" { + filter["source"] = leadFilters.Source + } + + if leadFilters.LandSizeInAcres != pb.LandSizeInAcres_UnspecifiedLandSize { + filter["landSizeInAcres"] = leadFilters.LandSizeInAcres.String() + } + + if leadFilters.FarmingType != pb.FarmingType_UnspecifiedFarming { + filter["farmingType"] = leadFilters.FarmingType.String() + } + + // if certification details are not nil then add it to filter if not empty + if leadFilters.CertificationDetails != nil { + + filter["certificationDetails.isCertified"] = leadFilters.CertificationDetails.IsCertified + + if leadFilters.CertificationDetails.CertificationAgency != "" { + filter["certificationDetails.certificationAgency"] = leadFilters.CertificationDetails.CertificationAgency + } + if leadFilters.CertificationDetails.CertificationName != "" { + filter["certificationDetails.certificationName"] = leadFilters.CertificationDetails.CertificationName + } + } + + if leadFilters.MainProfession != "" { + filter["mainProfession"] = leadFilters.MainProfession + } + + if leadFilters.OrganizationName != "" { + filter["organizationName"] = leadFilters.OrganizationName + } + + if leadFilters.SideProfession != "" { + filter["sideProfession"] = leadFilters.SideProfession + } + + if leadFilters.Education != "" { + filter["education"] = leadFilters.Education + } + + if leadFilters.Status != pb.Status_UNSPECIFIED_STATUS { + filter["status"] = leadFilters.Status.String() + } + + if leadFilters.AddressFilters != nil { + addressFilters := getAddressFilter(leadFilters.AddressFilters) + + if len(addressFilters) > 0 { + filter["addresses"] = bson.M{ + "$elemMatch": addressFilters, + } + } + } + + return filter +} + +func getAddressFilter(addressfilter *pb.AddressFilters) bson.M { + filter := bson.M{} + if addressfilter.City != "" { + filter["city"] = addressfilter.City + } + if addressfilter.State != "" { + filter["state"] = addressfilter.State + } + if addressfilter.Country != "" { + filter["country"] = addressfilter.Country + } + return filter +} From 01d435a4497a332f3572082dcbd0a2a1063e227a Mon Sep 17 00:00:00 2001 From: Rajeev Singh Date: Sun, 10 Mar 2024 19:19:34 +0530 Subject: [PATCH 4/5] updated auth model --- auth-model | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth-model b/auth-model index 15ee1ed..1120549 160000 --- a/auth-model +++ b/auth-model @@ -1 +1 @@ -Subproject commit 15ee1ed9a5f6b46c2249dfaec3000fc2b69db6e8 +Subproject commit 1120549039c8a7067323f221d1da33b69cb3a5bb From 88d39a10092795394f804a73cdfd2b8d475b0180 Mon Sep 17 00:00:00 2001 From: Rajeev Singh Date: Thu, 14 Mar 2024 23:41:14 +0530 Subject: [PATCH 5/5] updated submodule commit --- auth-model | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth-model b/auth-model index 1120549..6290a19 160000 --- a/auth-model +++ b/auth-model @@ -1 +1 @@ -Subproject commit 1120549039c8a7067323f221d1da33b69cb3a5bb +Subproject commit 6290a191fcb7d56ed793ccd3fa558c2a01b60639