Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions api/handlers/scheme.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package handlers

import (
"encoding/json"
"net/http"

"github.com/ONEST-Network/Job-Manager-Adapter/internal/scheme"
"github.com/ONEST-Network/Job-Manager-Adapter/pkg/clients"
schemePayload "github.com/ONEST-Network/Job-Manager-Adapter/pkg/types/payload/scheme"
"github.com/gin-gonic/gin"
)

// @Summary Push Scheme
// @Description Add a new scheme to the manager
// @Tags Scheme
// @Accept json
// @Produce json
// @Param request body schemePayload.AddSchemeRequest true "request body"
// @Success 200
// @Failure 500 {object} string
// @Router /scheme/push [post]
func PushScheme(clients *clients.Clients) gin.HandlerFunc {
return func(c *gin.Context) {
var payload schemePayload.AddSchemeRequest
if err := json.NewDecoder(c.Request.Body).Decode(&payload); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}

if err := scheme.NewScheme(clients).AddScheme(&payload); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
return
}

c.JSON(http.StatusOK, gin.H{"message": "Scheme added successfully"})
}
}

// @Summary Get scheme applications
// @Description Get applications for a specific scheme
// @Tags Scheme
// @Accept json
// @Produce json
// @Param id path string true "Scheme ID"
// @Success 200 {array} schemePayload.GetSchemeApplicationsResponse
// @Failure 500 {object} string
// @Router /scheme/{id}/applications [get]
func GetSchemeApplications(clients *clients.Clients) gin.HandlerFunc {
return func(c *gin.Context) {
schemeID := c.Param("id")

applications, err := scheme.NewScheme(clients).GetSchemeApplications(schemeID)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
return
}

c.JSON(http.StatusOK, applications)
}
}

// @Summary Update scheme application status
// @Description Update the status of a scheme application
// @Tags Scheme
// @Accept json
// @Produce json
// @Param request body schemePayload.UpdateSchemeStatusRequest true "request body"
// @Success 200
// @Failure 500 {object} string
// @Router /scheme/status [patch]
func UpdateSchemeStatus(clients *clients.Clients) gin.HandlerFunc {
return func(c *gin.Context) {
var payload schemePayload.UpdateSchemeStatusRequest
if err := json.NewDecoder(c.Request.Body).Decode(&payload); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}

if err := scheme.NewScheme(clients).UpdateSchemeApplicationStatus(payload.ApplicationID, &payload); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
return
}

c.JSON(http.StatusOK, gin.H{"message": "Status updated successfully"})
}
}

// @Summary Apply to Scheme
// @Description Submit an application for a scheme (Triggers auto-eligibility)
// @Tags Scheme
// @Accept json
// @Produce json
// @Param id path string true "Scheme ID"
// @Param request body schemePayload.JobApplicationRequest true "request body"
// @Success 200 {object} string "Application ID"
// @Failure 500 {object} string
// @Router /scheme/{id}/apply [post]
func ApplyToScheme(clients *clients.Clients) gin.HandlerFunc {
return func(c *gin.Context) {
schemeID := c.Param("id")
var payload schemePayload.JobApplicationRequest
if err := json.NewDecoder(c.Request.Body).Decode(&payload); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}

appID, err := scheme.NewScheme(clients).ApplyToScheme(schemeID, &payload)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
return
}

c.JSON(http.StatusOK, gin.H{"applicationId": appID})
}
}
14 changes: 14 additions & 0 deletions api/routes/scheme.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package routes

import (
"github.com/ONEST-Network/Job-Manager-Adapter/api/handlers"
"github.com/ONEST-Network/Job-Manager-Adapter/pkg/clients"
"github.com/gin-gonic/gin"
)

func SchemeRouter(router *gin.RouterGroup, clients *clients.Clients) {
router.POST("/push", handlers.PushScheme(clients))
router.GET("/:id/applications", handlers.GetSchemeApplications(clients))
router.PATCH("/status", handlers.UpdateSchemeStatus(clients))
router.POST("/:id/apply", handlers.ApplyToScheme(clients))
}
Binary file added bpp.exe
Binary file not shown.
16 changes: 16 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: '3.8'

services:
mongodb:
image: mongo:6.0
container_name: mongodb-bpp
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: password
volumes:
- mongo-data:/data/db

volumes:
mongo-data:
59 changes: 59 additions & 0 deletions internal/scheme/eligibility.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package scheme

import (
"github.com/ONEST-Network/Job-Manager-Adapter/pkg/database/mongodb/job"
"github.com/ONEST-Network/Job-Manager-Adapter/pkg/database/mongodb/job-application"
)

// ValidateEligibility compares applicant details against scheme requirements
// Returns an eligibility score (0.0 to 1.0) and whether they are eligible
func ValidateEligibility(criteria job.Eligibility, applicant jobapplication.ApplicantDetails) (float64, bool) {
score := 0.0
totalWeight := 3.0

// 1. Gender check
if criteria.Gender == job.GenderAny || string(criteria.Gender) == applicant.Gender {
score += 1.0
}

// 2. Experience check
if applicant.Experience.Years >= criteria.YearsOfExperience {
score += 1.0
} else if criteria.YearsOfExperience > 0 {
score += float64(applicant.Experience.Years) / float64(criteria.YearsOfExperience)
}

// 3. Academic Qualification check
if isQualified(criteria.AcademicQualification, applicant) {
score += 1.0
}

finalScore := score / totalWeight
isEligible := finalScore >= 0.7 // Threshold for eligibility

return finalScore, isEligible
}

func isQualified(required job.AcademicQualification, applicant jobapplication.ApplicantDetails) bool {
// Simplified qualification hierarchy check
qualMap := map[job.AcademicQualification]int{
job.AcademicQualificationNone: 0,
job.AcademicQualificationClassX: 1,
job.AcademicQualificationClassXII: 2,
job.AcademicQualificationDiploma: 3,
job.AcademicQualificationGraduate: 4,
job.AcademicQualificationPostGraduate: 5,
}

// We don't have applicant's qualification directly in ApplicantDetails,
// but we could infer it or assume it's part of a complete profile.
// For now, let's assume applicant has at least some qualification if age > 18
applicantQual := job.AcademicQualificationNone
if applicant.Age >= 22 {
applicantQual = job.AcademicQualificationGraduate
} else if applicant.Age >= 18 {
applicantQual = job.AcademicQualificationClassXII
}

return qualMap[applicantQual] >= qualMap[required]
}
146 changes: 146 additions & 0 deletions internal/scheme/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package scheme

import (
"fmt"
"time"

"github.com/ONEST-Network/Job-Manager-Adapter/pkg/clients"
"github.com/ONEST-Network/Job-Manager-Adapter/pkg/database/mongodb/scheme"
"github.com/ONEST-Network/Job-Manager-Adapter/pkg/database/mongodb/scheme-application"
schemePayload "github.com/ONEST-Network/Job-Manager-Adapter/pkg/types/payload/scheme"
"github.com/ONEST-Network/Job-Manager-Adapter/pkg/utils/random"
"github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/bson"
)

type Interface interface {
AddScheme(payload *schemePayload.AddSchemeRequest) error
GetSchemeApplications(schemeID string) ([]schemePayload.GetSchemeApplicationsResponse, error)
UpdateSchemeApplicationStatus(applicationId string, payload *schemePayload.UpdateSchemeStatusRequest) error
ApplyToScheme(schemeID string, payload *schemePayload.JobApplicationRequest) (string, error)
}

type Scheme struct {
clients *clients.Clients
}

func NewScheme(clients *clients.Clients) Interface {
return &Scheme{
clients: clients,
}
}

func (s *Scheme) AddScheme(payload *schemePayload.AddSchemeRequest) error {
logrus.Infof("[Request]: Received request to add a new scheme: %s", payload.Name)

business, err := s.clients.BusinessClient.GetBusiness(payload.BusinessID)
if err != nil {
return fmt.Errorf("failed to get business with id %s, %v", payload.BusinessID, err)
}

var schemeObj = &scheme.Scheme{
ID: payload.ID,
Name: payload.Name,
Description: payload.Description,
Type: payload.Type,
Business: *business,
Eligibility: payload.Eligibility,
Location: payload.Location,
FundingAmount: payload.FundingAmount,
Category: payload.Category,
}

if err := s.clients.SchemeClient.CreateScheme(schemeObj); err != nil {
logrus.Errorf("Failed to create scheme, %v", err)
return err
}

return nil
}

func (s *Scheme) GetSchemeApplications(schemeID string) ([]schemePayload.GetSchemeApplicationsResponse, error) {
var response []schemePayload.GetSchemeApplicationsResponse

logrus.Infof("[Request]: Received request to get applications for scheme %s", schemeID)

schemeObj, err := s.clients.SchemeClient.GetScheme(schemeID)
if err != nil {
return nil, fmt.Errorf("failed to get scheme %s, %v", schemeID, err)
}

for _, appId := range schemeObj.ApplicationIDs {
app, err := s.clients.SchemeApplicationClient.GetSchemeApplication(appId)
if err != nil {
logrus.Errorf("Failed to get scheme application %s, %v", appId, err)
continue
}

response = append(response, schemePayload.GetSchemeApplicationsResponse{
ID: app.ID,
ApplicantDetails: app.ApplicantDetails,
Status: app.Status,
EligibilityScore: app.EligibilityScore,
})
}

return response, nil
}

func (s *Scheme) UpdateSchemeApplicationStatus(applicationId string, payload *schemePayload.UpdateSchemeStatusRequest) error {
logrus.Infof("[Request]: Received request to update scheme application %s status", applicationId)

var (
query = bson.D{{Key: "id", Value: applicationId}}
update = bson.D{{Key: "$set", Value: bson.D{{Key: "status", Value: payload.Status}, {Key: "updated_at", Value: time.Now()}}}}
)

if err := s.clients.SchemeApplicationClient.UpdateSchemeApplication(query, update); err != nil {
logrus.Errorf("Failed to update status, %v", err)
return err
}

return nil
}

func (s *Scheme) ApplyToScheme(schemeID string, payload *schemePayload.JobApplicationRequest) (string, error) {
logrus.Infof("[Request]: Received application for scheme %s from %s", schemeID, payload.ApplicantDetails.Name)

schemeObj, err := s.clients.SchemeClient.GetScheme(schemeID)
if err != nil {
return "", fmt.Errorf("scheme not found, %v", err)
}

// Automatic Eligibility Validation
score, eligible := ValidateEligibility(schemeObj.Eligibility, payload.ApplicantDetails)

status := schemeapplication.SchemeStatusSubmitted
if !eligible {
status = schemeapplication.SchemeStatusIneligible
} else {
status = schemeapplication.SchemeStatusEligible
}

appID := random.GetRandomString(10)
app := &schemeapplication.SchemeApplication{
ID: appID,
SchemeID: schemeID,
ApplicantDetails: payload.ApplicantDetails,
Status: status,
EligibilityScore: score,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}

if err := s.clients.SchemeApplicationClient.CreateSchemeApplication(app); err != nil {
return "", err
}

// Update Scheme with new Application ID
query := bson.D{{Key: "id", Value: schemeID}}
update := bson.D{{Key: "$push", Value: bson.D{{Key: "application_ids", Value: appID}}}}
if err := s.clients.SchemeClient.UpdateScheme(query, update); err != nil {
return "", err
}

return appID, nil
}
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ func main() {
proxy.SetProxyENVs()

// Initialize mongodb clients
businessClient, jobClient, jobApplicationClient, initJobApplication := server.InitMongoDB()
businessClient, jobClient, jobApplicationClient, initJobApplication, schemeClient, schemeApplicationClient := server.InitMongoDB()

// Set up clients
clients := clients.NewClients(jobClient, businessClient, jobApplicationClient, initJobApplication)
clients := clients.NewClients(jobClient, businessClient, jobApplicationClient, initJobApplication, schemeClient, schemeApplicationClient)

// initialize the server
server := server.SetupServer(clients)
Expand Down
8 changes: 7 additions & 1 deletion pkg/clients/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
dbInitJobApplication "github.com/ONEST-Network/Job-Manager-Adapter/pkg/database/mongodb/init-job-application"
dbJob "github.com/ONEST-Network/Job-Manager-Adapter/pkg/database/mongodb/job"
dbJobApplication "github.com/ONEST-Network/Job-Manager-Adapter/pkg/database/mongodb/job-application"
dbScheme "github.com/ONEST-Network/Job-Manager-Adapter/pkg/database/mongodb/scheme"
dbSchemeApplication "github.com/ONEST-Network/Job-Manager-Adapter/pkg/database/mongodb/scheme-application"
)

type Clients struct {
Expand All @@ -14,14 +16,18 @@ type Clients struct {
BusinessClient *dbBusiness.Dao
JobApplicationClient *dbJobApplication.Dao
InitJobApplicationClient *dbInitJobApplication.Dao
SchemeClient *dbScheme.Dao
SchemeApplicationClient *dbSchemeApplication.Dao
}

func NewClients(jobClient *dbJob.Dao, businessClient *dbBusiness.Dao, jobApplicationClient *dbJobApplication.Dao, initJobApplicationClient *dbInitJobApplication.Dao) *Clients {
func NewClients(jobClient *dbJob.Dao, businessClient *dbBusiness.Dao, jobApplicationClient *dbJobApplication.Dao, initJobApplicationClient *dbInitJobApplication.Dao, schemeClient *dbScheme.Dao, schemeApplicationClient *dbSchemeApplication.Dao) *Clients {
return &Clients{
ApiClient: apiclient.NewAPIClient(),
JobClient: jobClient,
BusinessClient: businessClient,
JobApplicationClient: jobApplicationClient,
InitJobApplicationClient: initJobApplicationClient,
SchemeClient: schemeClient,
SchemeApplicationClient: schemeApplicationClient,
}
}
Loading