├── application ├── util.pvf_verification.go ├── config.calendar.go ├── util.student.middleware.go ├── util.company_id.go ├── admin.count.go ├── util.student_rcid.go ├── admin.email.go ├── util.company.middleware.go ├── student.events.go ├── db.question.go ├── config.go ├── student.student.go ├── company.student.go ├── student.proforma.go ├── model.hooks.go ├── db.events.go ├── company.application.go ├── admin.pio_ppo.go ├── db.pvf.go ├── verify.pvf.go ├── student.pvf.go ├── util.calendar.go ├── company.proforma.go ├── admin.questions.go └── company.events.go ├── .github ├── images │ ├── logo.png │ └── screenshot.png ├── workflows │ ├── go.yml │ ├── go-lint.yml │ ├── gosec.yml │ └── codeql.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── config ├── init.go ├── logrus.go └── viper.go ├── scripts ├── production.sh └── createDB.sh ├── constants └── role.go ├── secret.yml.template ├── company ├── util.companyid.go ├── admin.companyHRs.go ├── model.go ├── db.hr.go ├── config.go ├── router.go ├── company.hr.go ├── db.company.go ├── admin.HR.go ├── admin.get_company.go └── admin.company.go ├── util ├── convert.go └── prog_dept.go ├── ras ├── router.go └── hello.go ├── mail ├── generate.go ├── config.go └── service.go ├── .gitignore ├── rc ├── db.answers.go ├── student.notice.go ├── util.student.middleware.go ├── util.company_id.go ├── util.active_rc.middleware.go ├── util.student_id.go ├── company.info.go ├── admin.count.go ├── db.notice.go ├── student.rc.go ├── config.go ├── db.question.go ├── student.resume.go ├── admin.clarification.go ├── student.enrollment.go ├── admin.answers.go ├── db.rc.go ├── db.resume.go ├── admin.question.go ├── router.go ├── db.company.go ├── admin.resume.go ├── admin.rc.go └── admin.notice.go ├── secret.GCPcredentials.json.template ├── .vscode └── launch.json ├── auth ├── db.company.go ├── util.hash.go ├── user.whoami.go ├── user.companies.go ├── user.credits.go ├── db.otp.go ├── router.go ├── model.go ├── config.go ├── user.login.go ├── company.signup.go ├── util.otp.go ├── admin.actions.go ├── user.reset_password.go ├── god.reset_password.go ├── god.signup.go ├── god.login.go ├── user.signup.go ├── db.user.go └── user.user_db.go ├── student ├── student.get.go ├── admin.delete.go ├── config.go ├── student.update.go ├── router.go ├── db.document.go ├── admin.clarification.go ├── student.document.go ├── admin.student.go ├── model.go ├── admin.document.go └── admin.update.go ├── config.yaml ├── middleware ├── cors.go ├── admin.go ├── jwt.go └── authenticator.go ├── cmd ├── auth.go ├── ras.go ├── verification.go ├── company.go ├── student.go ├── panic_alert.go ├── main.go └── admin.go ├── plugins └── notice.go ├── container ├── init.sql ├── Dockerfile └── nginx.conf ├── docker-compose.yml └── go.mod /application/util.pvf_verification.go: -------------------------------------------------------------------------------- 1 | package application 2 | -------------------------------------------------------------------------------- /.github/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spo-iitk/ras-backend/HEAD/.github/images/logo.png -------------------------------------------------------------------------------- /config/init.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | func init() { 4 | logrusConfig() 5 | viperConfig() 6 | } 7 | -------------------------------------------------------------------------------- /.github/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spo-iitk/ras-backend/HEAD/.github/images/screenshot.png -------------------------------------------------------------------------------- /scripts/production.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pull latest commit 4 | git pull origin main --force 5 | 6 | # Install `things` 7 | go get -d -v ./... 8 | go install -v ./... 9 | 10 | service nginx start 11 | go build -o server ./cmd 12 | ./server 13 | -------------------------------------------------------------------------------- /constants/role.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | type Role uint8 4 | 5 | const ( 6 | NONE Role = 0 7 | STUDENT Role = 1 8 | COMPANY Role = 2 9 | 10 | GOD Role = 100 11 | OPC Role = 101 12 | APC Role = 102 13 | CHAIR Role = 103 14 | COCO Role = 104 15 | STAFF Role = 105 16 | ) 17 | -------------------------------------------------------------------------------- /secret.yml.template: -------------------------------------------------------------------------------- 1 | MAIL: 2 | USER: "ias" 3 | PASS: "" 4 | WEBTEAM: "spowebteam@gmail.com" 5 | JWT: 6 | PRIVATE_KEY: "pretty-big-secret" 7 | DATABASE: 8 | # HOST: "localhost" 9 | PASSWORD: "b2Led2ke" 10 | CALENDAR: 11 | CID1: "" 12 | CID2: "" 13 | CID3: "" 14 | CID4: "" 15 | 16 | -------------------------------------------------------------------------------- /company/util.companyid.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/spo-iitk/ras-backend/middleware" 6 | ) 7 | 8 | func extractCompanyID(ctx *gin.Context) (uint, error) { 9 | user_email := middleware.GetUserID(ctx) 10 | return FetchCompanyIDByEmail(ctx, user_email) 11 | } 12 | -------------------------------------------------------------------------------- /util/convert.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | func ParseUint(s string) (uint, error) { 9 | i, err := strconv.ParseUint(s, 10, 32) 10 | if err != nil { 11 | return 0, err 12 | } 13 | return uint(i), nil 14 | } 15 | 16 | func ParseString(s uint) string { 17 | return fmt.Sprint(s) 18 | } 19 | -------------------------------------------------------------------------------- /ras/router.go: -------------------------------------------------------------------------------- 1 | package ras 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/spo-iitk/ras-backend/mail" 6 | ) 7 | 8 | func RASRouter(mail_channel chan mail.Mail, r *gin.Engine) { 9 | api := r.Group("/api/ras") 10 | { 11 | api.GET("", HelloWorldController) 12 | api.GET("/testmail", MailController(mail_channel)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /mail/generate.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | func GenerateMail(to, subject, body string) Mail { 4 | return Mail{ 5 | To: []string{to}, 6 | Subject: subject, 7 | Body: body, 8 | } 9 | } 10 | 11 | func GenerateMails(to []string, subject, body string) Mail { 12 | return Mail{ 13 | To: to, 14 | Subject: subject, 15 | Body: body, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | *.log 17 | secret.GCPcredentials.json 18 | secret.yml 19 | server 20 | -------------------------------------------------------------------------------- /config/logrus.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | ) 6 | 7 | func logrusConfig() { 8 | logrus.SetLevel(logrus.DebugLevel) 9 | logrus.SetReportCaller(true) 10 | 11 | // f, err := os.OpenFile("raslog.log", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) 12 | // if err != nil { 13 | // fmt.Printf("error opening file: %v", err) 14 | // panic(err) 15 | // } 16 | // logrus.SetOutput(f) 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: 1.18 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | -------------------------------------------------------------------------------- /rc/db.answers.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func createStudentAnswer(ctx *gin.Context, answer *RecruitmentCycleQuestionsAnswer) error { 6 | tx := db.WithContext(ctx). 7 | Where( 8 | "recruitment_cycle_question_id = ? AND student_recruitment_cycle_id = ?", 9 | answer.RecruitmentCycleQuestionID, 10 | answer.StudentRecruitmentCycleID, 11 | ).FirstOrCreate(answer) 12 | return tx.Error 13 | } 14 | -------------------------------------------------------------------------------- /company/admin.companyHRs.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import ( 4 | "net/http" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func getAllCompanyHRsHandler(ctx *gin.Context) { 9 | var companyHRs []CompanyHR 10 | 11 | err := getAllHRUserDB(ctx, &companyHRs) 12 | 13 | if err != nil { 14 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 15 | return 16 | } 17 | 18 | ctx.JSON(http.StatusOK, companyHRs) 19 | } 20 | -------------------------------------------------------------------------------- /rc/student.notice.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func getAllNoticesForStudentHandler(ctx *gin.Context) { 10 | rid := ctx.Param("rid") 11 | var notices []Notice 12 | 13 | err := fetchAllNotices(ctx, rid, ¬ices) 14 | if err != nil { 15 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 16 | return 17 | } 18 | 19 | ctx.JSON(http.StatusOK, notices) 20 | } 21 | -------------------------------------------------------------------------------- /secret.GCPcredentials.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "type": "service_account", 3 | "project_id": "pid", 4 | "private_key_id": "privateid", 5 | "private_key": "pee-key", 6 | "client_email": "rasdemigod@spo.iitk", 7 | "client_id": "cid", 8 | "auth_uri": "https://accounts.google.com/", 9 | "token_uri": "https://oauth2.googleapis.com/", 10 | "auth_provider_x509_cert_url": "https://www.googleapis.com/", 11 | "client_x509_cert_url": "https://www.googleapis.com/" 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Package", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "/home/salazar/go/src/github.com/spo-iitk/ras-backend/cmd/." 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /application/config.calendar.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "google.golang.org/api/calendar/v3" 8 | "google.golang.org/api/option" 9 | ) 10 | 11 | var cal_srv *calendar.Service 12 | 13 | func gCalendarConnect() { 14 | ctxb := context.Background() 15 | srv, err := calendar.NewService(ctxb, option.WithCredentialsFile("./secret.GCPcredentials.json")) 16 | if err != nil { 17 | log.Fatalf("Unable to retrieve Calendar client: %v", err) 18 | } 19 | cal_srv = srv 20 | } 21 | -------------------------------------------------------------------------------- /auth/db.company.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func createCompany(ctx *gin.Context, company *CompanySignUpRequest) (uint, error) { 6 | tx := db.WithContext(ctx).Create(company) 7 | return company.ID, tx.Error 8 | } 9 | 10 | func getAllCompaniesAdded(ctx *gin.Context) ([]string, error) { 11 | var companies []string 12 | tx := db.WithContext(ctx).Model(&CompanySignUpRequest{}).Order("created_at DESC"). 13 | Limit(50).Pluck("company_name", &companies) 14 | return companies, tx.Error 15 | } 16 | -------------------------------------------------------------------------------- /auth/util.hash.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | 6 | "golang.org/x/crypto/bcrypt" 7 | ) 8 | 9 | func hashAndSalt(password string) string { 10 | hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 11 | if err != nil { 12 | logrus.Info(err) 13 | } 14 | return string(hash) 15 | } 16 | 17 | func comparePasswords(hashedPwd string, plainPwd string) bool { 18 | err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd)) 19 | return err == nil 20 | } 21 | -------------------------------------------------------------------------------- /student/student.get.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/middleware" 8 | ) 9 | 10 | func getStudentHandler(ctx *gin.Context) { 11 | var student Student 12 | email := middleware.GetUserID(ctx) 13 | 14 | err := getStudentByEmail(ctx, &student, email) 15 | 16 | if err != nil { 17 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 18 | return 19 | } 20 | 21 | ctx.JSON(http.StatusOK, student) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /rc/util.student.middleware.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func ensureActiveStudent() gin.HandlerFunc { 10 | return func(ctx *gin.Context) { 11 | id, _, err := extractStudentRCID(ctx) 12 | 13 | if err != nil { 14 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 15 | return 16 | } 17 | 18 | ctx.Set("student_rc_id", int(id)) 19 | } 20 | } 21 | 22 | func getStudentRCID(ctx *gin.Context) uint { 23 | return uint(ctx.GetInt("student_rc_id")) 24 | } 25 | -------------------------------------------------------------------------------- /config/viper.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | 6 | "github.com/spf13/viper" 7 | ) 8 | 9 | func viperConfig() { 10 | viper.SetConfigType("yaml") 11 | viper.AddConfigPath(".") 12 | 13 | viper.SetConfigName("config") 14 | err := viper.ReadInConfig() 15 | if err != nil { 16 | logrus.Fatalf("Fatal error config file: %s \n", err) 17 | panic(err) 18 | } 19 | 20 | viper.SetConfigName("secret") 21 | 22 | err = viper.MergeInConfig() 23 | if err != nil { 24 | logrus.Errorf("Fatal error secret file: %s \n", err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /util/prog_dept.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // array of all double major program department IDs 4 | /* 5 | 28: AE | 29: BSBE | 30: CE | 31: CHE | 6 | 32: CSE | 33: EE | 34: MSE | 35: ME | 7 | 96: CHM | 36: ECO | 37: MTH | 97: SDS | 8 | 98: PHY | 9 | */ 10 | var doubleMajorProgramDepartmentIDs = []uint{28, 29, 30, 31, 32, 33, 34, 35, 96, 36, 37, 97, 98} 11 | 12 | func IsDoubleMajor(programDepartmentID uint) bool { 13 | for _, id := range doubleMajorProgramDepartmentIDs { 14 | if id == programDepartmentID { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | -------------------------------------------------------------------------------- /application/util.student.middleware.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func ensureActiveStudent() gin.HandlerFunc { 10 | return func(ctx *gin.Context) { 11 | id, err := extractStudentRCID(ctx) 12 | 13 | if err != nil { 14 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 15 | return 16 | } 17 | 18 | ctx.Set("student_rc_id", int(id)) 19 | 20 | ctx.Next() 21 | } 22 | } 23 | 24 | func getStudentRCID(ctx *gin.Context) uint { 25 | return uint(ctx.GetInt("student_rc_id")) 26 | } 27 | -------------------------------------------------------------------------------- /company/model.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Company struct { 6 | gorm.Model 7 | Name string `json:"name"` 8 | Tags string `json:"tags"` 9 | Website string `json:"website"` 10 | Description string `json:"description"` 11 | } 12 | 13 | type CompanyHR struct { 14 | gorm.Model 15 | CompanyID uint `json:"company_id"` 16 | Company Company `gorm:"foreignkey:CompanyID" json:"-"` 17 | Name string `json:"name"` 18 | Email string `gorm:"uniqueIndex;->;<-:create" json:"email"` 19 | Phone string `json:"phone"` 20 | Designation string `json:"designation"` 21 | } 22 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | MAIL: 2 | HOST: "patra.iitk.ac.in" 3 | PORT: "587" 4 | BATCH: 200 5 | JWT: 6 | EXPIRATION: 7 | LONG: 5000 8 | SHORT: 200 9 | PVF: 10 | EXPIRATION: 10080 #7days 11 | OTP: 12 | EXPIRATION: 20 13 | SIZE: 6 14 | DATABASE: 15 | HOST: "database" 16 | PORT: "5432" 17 | USER: "admin" 18 | DBNAME: 19 | APPLICATION: "application" 20 | COMPANY: "company" 21 | RC: "rc" 22 | STUDENT: "student" 23 | AUTH: "auth" 24 | PORT: 25 | RAS: 3470 26 | AUTH: 3475 27 | STUDENT: 3480 28 | COMPANY: 3485 29 | VERIFICATION: 3505 30 | ADMIN: 31 | RC: 3490 32 | APP: 3492 33 | COMPANY: 3495 34 | STUDENT: 3500 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /auth/user.whoami.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/middleware" 8 | ) 9 | 10 | func whoamiHandler(ctx *gin.Context) { 11 | middleware.Authenticator()(ctx) 12 | user_id := middleware.GetUserID(ctx) 13 | role_id := middleware.GetRoleID(ctx) 14 | 15 | if user_id == "" { 16 | return 17 | } 18 | 19 | var user User 20 | err := fetchUser(ctx, &user, user_id) 21 | if err != nil { 22 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"}) 23 | return 24 | } 25 | 26 | ctx.JSON(http.StatusOK, gin.H{"role_id": role_id, "user_id": user_id, "name": user.Name}) 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/go-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 12 | # pull-requests: read 13 | 14 | jobs: 15 | golangci: 16 | name: lint 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.19' 23 | cache: false 24 | - name: golangci-lint 25 | uses: golangci/golangci-lint-action@v3 26 | with: 27 | version: latest 28 | -------------------------------------------------------------------------------- /mail/config.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "github.com/spf13/viper" 6 | _ "github.com/spo-iitk/ras-backend/config" 7 | ) 8 | 9 | var ( 10 | user string 11 | pass string 12 | host string 13 | port string 14 | webteam string 15 | batch int 16 | sender string 17 | ) 18 | 19 | func init() { 20 | logrus.Info("Initializing mailer") 21 | 22 | user = viper.GetString("MAIL.USER") 23 | sender = user + "@iitk.ac.in" 24 | 25 | pass = viper.GetString("MAIL.PASS") 26 | host = viper.GetString("MAIL.HOST") 27 | port = viper.GetString("MAIL.PORT") 28 | webteam = viper.GetString("MAIL.WEBTEAM") 29 | 30 | batch = viper.GetInt("MAIL.BATCH") 31 | } 32 | -------------------------------------------------------------------------------- /middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func CORS() gin.HandlerFunc { 6 | return func(c *gin.Context) { 7 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") 8 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") 9 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") 10 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") 11 | 12 | if c.Request.Method == "OPTIONS" { 13 | c.AbortWithStatus(204) 14 | return 15 | } 16 | 17 | c.Next() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /rc/util.company_id.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/spo-iitk/ras-backend/company" 6 | "github.com/spo-iitk/ras-backend/middleware" 7 | ) 8 | 9 | // func extractCompanyRCID(ctx *gin.Context) (uint, error) { 10 | // companyID, err := extractCompanyID(ctx) 11 | // if err != nil { 12 | // return 0, err 13 | // } 14 | 15 | // rid, err := util.ParseUint(ctx.Param("rid")) 16 | // if err != nil { 17 | // return 0, err 18 | // } 19 | 20 | // return FetchCompanyRCID(ctx, rid, companyID) 21 | // } 22 | 23 | func extractCompanyID(ctx *gin.Context) (uint, error) { 24 | user_email := middleware.GetUserID(ctx) 25 | return company.FetchCompanyIDByEmail(ctx, user_email) 26 | } 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Request]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /scripts/createDB.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CreateDB() { 4 | echo "Creating DB $1" 5 | sudo -u postgres psql -c "CREATE ROLE $1admin WITH LOGIN PASSWORD 'b2Led2ke';" 6 | sudo -u postgres psql -c "CREATE DATABASE $1;" 7 | sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $1 TO $1admin;" 8 | # echo "CREATE ROLE $1admin WITH LOGIN PASSWORD 'b2Led2ke';" >> container/init.sql 9 | # echo "CREATE DATABASE $1;" >> container/init.sql 10 | # echo "GRANT ALL PRIVILEGES ON DATABASE $1 TO $1admin;" >> container/init.sql 11 | # echo "" >> container/init.sql 12 | } 13 | 14 | sudo systemctl start postgresql 15 | CreateDB application 16 | CreateDB auth 17 | CreateDB company 18 | CreateDB rc 19 | CreateDB student 20 | -------------------------------------------------------------------------------- /student/admin.delete.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func deleteStudentHandler(ctx *gin.Context) { 12 | 13 | sid, err := strconv.ParseUint(ctx.Param("sid"), 10, 32) 14 | if err != nil { 15 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 16 | return 17 | } 18 | 19 | err = deleteStudent(ctx, uint(sid)) 20 | if err != nil { 21 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 22 | return 23 | } 24 | 25 | logrus.Infof("A student with id %d is deleted", sid) 26 | 27 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully deleted"}) 28 | 29 | } 30 | -------------------------------------------------------------------------------- /application/util.company_id.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/spo-iitk/ras-backend/company" 6 | "github.com/spo-iitk/ras-backend/middleware" 7 | ) 8 | 9 | // func extractCompanyRCID(ctx *gin.Context) (uint, error) { 10 | // companyID, err := extractCompanyID(ctx) 11 | // if err != nil { 12 | // return 0, err 13 | // } 14 | 15 | // rid, err := util.ParseUint(ctx.Param("rid")) 16 | // if err != nil { 17 | // return 0, err 18 | // } 19 | 20 | // return rc.FetchCompanyRCID(ctx, rid, companyID) 21 | // } 22 | 23 | func extractCompanyID(ctx *gin.Context) (uint, error) { 24 | user_email := middleware.GetUserID(ctx) 25 | return company.FetchCompanyIDByEmail(ctx, user_email) 26 | } 27 | -------------------------------------------------------------------------------- /auth/user.companies.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/constants" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | ) 10 | 11 | func companiesAddedHandler(ctx *gin.Context) { 12 | middleware.Authenticator()(ctx) 13 | role := middleware.GetRoleID(ctx) 14 | if role != constants.OPC && role != constants.GOD { 15 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) 16 | return 17 | } 18 | 19 | companies, err := getAllCompaniesAdded(ctx) 20 | if err != nil { 21 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 22 | return 23 | } 24 | 25 | ctx.JSON(http.StatusOK, gin.H{"companies": companies}) 26 | } 27 | -------------------------------------------------------------------------------- /cmd/auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spf13/viper" 8 | "github.com/spo-iitk/ras-backend/auth" 9 | "github.com/spo-iitk/ras-backend/mail" 10 | "github.com/spo-iitk/ras-backend/middleware" 11 | ) 12 | 13 | func authServer(mail_channel chan mail.Mail) *http.Server { 14 | PORT := viper.GetString("PORT.AUTH") 15 | r := gin.New() 16 | r.Use(middleware.CORS()) 17 | r.Use(gin.CustomRecovery(recoveryHandler)) 18 | r.Use(gin.Logger()) 19 | 20 | auth.Router(mail_channel, r) 21 | 22 | server := &http.Server{ 23 | Addr: ":" + PORT, 24 | Handler: r, 25 | ReadTimeout: readTimeout, 26 | WriteTimeout: writeTimeout, 27 | } 28 | 29 | return server 30 | } 31 | -------------------------------------------------------------------------------- /plugins/notice.go: -------------------------------------------------------------------------------- 1 | package plugins 2 | 3 | import "github.com/spo-iitk/ras-backend/mail" 4 | 5 | // was used in rc/admin.notice.go::postNoticeHandler is stale now 6 | func NewNoticeNotification(mail_channel chan mail.Mail, id uint, recruitmentCycleID uint, title string, description string, createdBy string) { 7 | if recruitmentCycleID != 6 { 8 | return 9 | } 10 | var emails []string = []string{"harshitr20@iitk.ac.in"} 11 | message := "A new notice has been created by " + createdBy + " with title " + title + " in Placement 2023-24 Phase 1.\n\nDescription: " + description + "\n\nClick here to view the notice: https://placement.iitk.ac.in/student/rc/6/notices" 12 | mail_channel <- mail.GenerateMails(emails, "God Notice: "+title, message) 13 | } 14 | -------------------------------------------------------------------------------- /rc/util.active_rc.middleware.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func checkAdminAccessToRC() gin.HandlerFunc { 10 | return func(ctx *gin.Context) { 11 | roleID := ctx.GetInt("roleID") 12 | if roleID > 101 && !checkIsActiveRC(ctx) { 13 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) 14 | return 15 | } 16 | ctx.Next() 17 | } 18 | } 19 | 20 | func checkIsActiveRC(ctx *gin.Context) bool { 21 | id := ctx.Param("rid") 22 | var rc RecruitmentCycle 23 | err := fetchRC(ctx, id, &rc) 24 | if err != nil { 25 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 26 | return false 27 | } 28 | return rc.IsActive 29 | } 30 | -------------------------------------------------------------------------------- /container/init.sql: -------------------------------------------------------------------------------- 1 | CREATE ROLE applicationadmin WITH LOGIN PASSWORD 'b2Led2ke'; 2 | CREATE DATABASE application; 3 | GRANT ALL PRIVILEGES ON DATABASE application TO applicationadmin; 4 | 5 | CREATE ROLE authadmin WITH LOGIN PASSWORD 'b2Led2ke'; 6 | CREATE DATABASE auth; 7 | GRANT ALL PRIVILEGES ON DATABASE auth TO authadmin; 8 | 9 | CREATE ROLE companyadmin WITH LOGIN PASSWORD 'b2Led2ke'; 10 | CREATE DATABASE company; 11 | GRANT ALL PRIVILEGES ON DATABASE company TO companyadmin; 12 | 13 | CREATE ROLE rcadmin WITH LOGIN PASSWORD 'b2Led2ke'; 14 | CREATE DATABASE rc; 15 | GRANT ALL PRIVILEGES ON DATABASE rc TO rcadmin; 16 | 17 | CREATE ROLE studentadmin WITH LOGIN PASSWORD 'b2Led2ke'; 18 | CREATE DATABASE student; 19 | GRANT ALL PRIVILEGES ON DATABASE student TO studentadmin; 20 | -------------------------------------------------------------------------------- /cmd/ras.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spf13/viper" 8 | "github.com/spo-iitk/ras-backend/mail" 9 | "github.com/spo-iitk/ras-backend/middleware" 10 | "github.com/spo-iitk/ras-backend/ras" 11 | ) 12 | 13 | func rasServer(mail_channel chan mail.Mail) *http.Server { 14 | PORT := viper.GetString("PORT.RAS") 15 | engine := gin.New() 16 | engine.Use(middleware.CORS()) 17 | // engine.Use(middleware.Authenticator()) 18 | ras.RASRouter(mail_channel, engine) 19 | engine.Use(gin.CustomRecovery(recoveryHandler)) 20 | engine.Use(gin.Logger()) 21 | 22 | server := &http.Server{ 23 | Addr: ":" + PORT, 24 | Handler: engine, 25 | ReadTimeout: readTimeout, 26 | WriteTimeout: writeTimeout, 27 | } 28 | return server 29 | } 30 | -------------------------------------------------------------------------------- /auth/user.credits.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/spo-iitk/ras-backend/middleware" 11 | ) 12 | 13 | func file() *os.File { 14 | f, err := os.OpenFile("credits.log", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) 15 | if err != nil { 16 | fmt.Printf("error opening file: %v", err) 17 | log.Fatal(err) 18 | } 19 | return f 20 | } 21 | 22 | func creditsHandler(ctx *gin.Context) { 23 | middleware.Authenticator()(ctx) 24 | user_id := middleware.GetUserID(ctx) 25 | role_id := middleware.GetRoleID(ctx) 26 | 27 | log.SetOutput(logf) 28 | log.Println("User:", user_id, "Role:", role_id) 29 | 30 | if user_id == "" { 31 | return 32 | } 33 | 34 | ctx.JSON(http.StatusOK, gin.H{"role_id": role_id, "user_id": user_id}) 35 | } 36 | -------------------------------------------------------------------------------- /ras/hello.go: -------------------------------------------------------------------------------- 1 | package ras 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/mail" 8 | ) 9 | 10 | func HelloWorldController(c *gin.Context) { 11 | c.JSON(http.StatusOK, gin.H{ 12 | "message": "Hello World!", 13 | }) 14 | } 15 | 16 | func PlaceHolderController(c *gin.Context) { 17 | c.JSON(http.StatusBadRequest, gin.H{ 18 | "message": "Please Implement me!", 19 | }) 20 | } 21 | 22 | func MailController(mail_channel chan mail.Mail) gin.HandlerFunc { 23 | return func(c *gin.Context) { 24 | mail_channel <- mail.GenerateMail("yashc22@iitk.ac.in", "Test Mail", "Hello World!") 25 | mail_channel <- mail.GenerateMails([]string{"yashlm1017@gmail.com", "bmerchant22@iitk.ac.in"}, "Test Mail to multiple ppl", "Hello Worlds!") 26 | c.JSON(http.StatusOK, gin.H{"message": "Mail sent"}) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rc/util.student_id.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/middleware" 8 | "github.com/spo-iitk/ras-backend/util" 9 | ) 10 | 11 | func extractStudentRCID(ctx *gin.Context) (uint, bool, error) { 12 | rid, err := util.ParseUint(ctx.Param("rid")) 13 | if err != nil { 14 | return 0, false, err 15 | } 16 | 17 | if !IsRCActive(ctx, rid) { 18 | return 0, false, errors.New("recruitment cycle is not active") 19 | } 20 | 21 | email := middleware.GetUserID(ctx) 22 | 23 | var student StudentRecruitmentCycle 24 | err = fetchStudentByEmailAndRC(ctx, email, rid, &student) 25 | if err != nil { 26 | return 0, false, err 27 | } 28 | 29 | if student.IsFrozen { 30 | return 0, false, errors.New("student frozen") 31 | } 32 | 33 | return student.ID, student.IsVerified, err 34 | } 35 | -------------------------------------------------------------------------------- /rc/company.info.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/company" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | ) 10 | 11 | type companyWhoamiResponse struct { 12 | Name string `json:"name"` 13 | Email string `json:"email"` 14 | } 15 | 16 | func companyWhoamiHandler(ctx *gin.Context) { 17 | companyID, err := extractCompanyID(ctx) 18 | if err != nil { 19 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 20 | return 21 | } 22 | 23 | name, err := company.GetCompanyName(ctx, companyID) 24 | if err != nil { 25 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 26 | return 27 | } 28 | 29 | ctx.JSON(http.StatusOK, companyWhoamiResponse{ 30 | Name: name, 31 | Email: middleware.GetUserID(ctx), 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /middleware/admin.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/constants" 8 | ) 9 | 10 | func EnsureAdmin() gin.HandlerFunc { 11 | return func(ctx *gin.Context) { 12 | role := GetRoleID(ctx) 13 | 14 | if role != constants.OPC && role != constants.GOD { 15 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) 16 | return 17 | } 18 | 19 | ctx.Next() 20 | } 21 | } 22 | 23 | func EnsurePsuedoAdmin() gin.HandlerFunc { 24 | return func(ctx *gin.Context) { 25 | role := GetRoleID(ctx) 26 | 27 | if role != constants.OPC && role != constants.GOD && role != constants.APC && role != constants.CHAIR { 28 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) 29 | return 30 | } 31 | 32 | ctx.Next() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /auth/db.otp.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-gonic/gin" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | func saveOTP(ctx *gin.Context, otp *OTP) error { 11 | tx := db.WithContext(ctx).Create(&otp) 12 | return tx.Error 13 | } 14 | 15 | func verifyOTP(ctx *gin.Context, userID string, otp string) (bool, error) { 16 | var otpObj OTP 17 | tx := db.WithContext(ctx).Where("user_id = ? AND otp = ? AND expires > ?", userID, otp, time.Now().UnixMilli()).First(&otpObj) 18 | switch tx.Error { 19 | case nil: 20 | db.WithContext(ctx).Delete(&otpObj) 21 | return true, nil 22 | case gorm.ErrRecordNotFound: 23 | return false, nil 24 | default: 25 | return false, tx.Error 26 | } 27 | } 28 | 29 | func cleanupOTP() { 30 | for { 31 | db.Unscoped().Delete(OTP{}, "expires < ?", time.Now().Add(-24*time.Hour).UnixMilli()) 32 | time.Sleep(time.Hour * 24) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | database: 5 | image: postgres:14.7 6 | restart: always 7 | environment: 8 | - POSTGRES_USER=postgres 9 | - POSTGRES_PASSWORD=postgres 10 | ports: 11 | - "5432:5432" 12 | volumes: 13 | - ./container/init.sql:/docker-entrypoint-initdb.d/init.sql 14 | - data:/var/lib/postgresql/data 15 | server: 16 | build: 17 | context: . 18 | dockerfile: container/Dockerfile 19 | restart: always 20 | depends_on: 21 | - database 22 | networks: 23 | - default 24 | ports: 25 | - "80" 26 | volumes: 27 | data: 28 | # network with subnet configuration 29 | networks: 30 | default: 31 | driver: bridge 32 | ipam: 33 | driver: default 34 | config: 35 | - subnet: "192.168.3.0/24" 36 | # gateway: "192.168.3.1" 37 | -------------------------------------------------------------------------------- /cmd/verification.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/spf13/viper" 9 | "github.com/spo-iitk/ras-backend/application" 10 | "github.com/spo-iitk/ras-backend/mail" 11 | "github.com/spo-iitk/ras-backend/middleware" 12 | ) 13 | 14 | func verificationServer(mail_channel chan mail.Mail) *http.Server { 15 | PORT := viper.GetString("PORT.VERIFICATION") 16 | fmt.Print(PORT) 17 | engine := gin.New() 18 | engine.Use(middleware.CORS()) 19 | engine.Use(middleware.PVFAuthenticator()) 20 | engine.Use(gin.CustomRecovery(recoveryHandler)) 21 | engine.Use(gin.Logger()) 22 | 23 | application.PvfVerificationRouter(mail_channel, engine) 24 | 25 | server := &http.Server{ 26 | Addr: ":" + PORT, 27 | Handler: engine, 28 | ReadTimeout: readTimeout, 29 | WriteTimeout: writeTimeout, 30 | } 31 | 32 | return server 33 | } 34 | -------------------------------------------------------------------------------- /cmd/company.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spf13/viper" 8 | "github.com/spo-iitk/ras-backend/application" 9 | "github.com/spo-iitk/ras-backend/company" 10 | "github.com/spo-iitk/ras-backend/middleware" 11 | "github.com/spo-iitk/ras-backend/rc" 12 | ) 13 | 14 | func companyServer() *http.Server { 15 | PORT := viper.GetString("PORT.COMPANY") 16 | engine := gin.New() 17 | engine.Use(middleware.CORS()) 18 | engine.Use(middleware.Authenticator()) 19 | engine.Use(gin.CustomRecovery(recoveryHandler)) 20 | engine.Use(gin.Logger()) 21 | 22 | rc.CompanyRouter(engine) 23 | application.CompanyRouter(engine) 24 | company.CompanyRouter(engine) 25 | 26 | server := &http.Server{ 27 | Addr: ":" + PORT, 28 | Handler: engine, 29 | ReadTimeout: readTimeout, 30 | WriteTimeout: writeTimeout, 31 | } 32 | 33 | return server 34 | } 35 | -------------------------------------------------------------------------------- /application/admin.count.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/util" 8 | ) 9 | 10 | func getApplicationCountHandler(ctx *gin.Context) { 11 | var roleCount int 12 | var recruitmentCount int 13 | 14 | rid, err := util.ParseUint(ctx.Param("rid")) 15 | if err != nil { 16 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 17 | return 18 | } 19 | 20 | roleCount, err = fetchRolesCount(ctx, rid) 21 | if err != nil { 22 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 23 | return 24 | } 25 | 26 | recruitmentCount, err = fetchRecruitedCount(ctx, rid) 27 | if err != nil { 28 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 29 | return 30 | } 31 | 32 | ctx.JSON(http.StatusOK, gin.H{"roles": roleCount, "recruited": recruitmentCount}) 33 | } 34 | -------------------------------------------------------------------------------- /rc/admin.count.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func getRCCountHandler(ctx *gin.Context) { 11 | var studentCount int 12 | var companyCount int 13 | 14 | rid, err := strconv.ParseUint(ctx.Param("rid"), 10, 32) 15 | if err != nil { 16 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 17 | return 18 | } 19 | 20 | studentCount, err = getRegisteredStudentCount(ctx, uint(rid)) 21 | if err != nil { 22 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 23 | return 24 | } 25 | 26 | companyCount, err = getRegisteredCompanyCount(ctx, uint(rid)) 27 | if err != nil { 28 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 29 | return 30 | } 31 | 32 | ctx.JSON(http.StatusOK, gin.H{"registered_student": studentCount, "registered_company": companyCount}) 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/gosec.yml: -------------------------------------------------------------------------------- 1 | name: "Security Scan" 2 | 3 | # Run workflow each time code is pushed to your repository and on a schedule. 4 | # The scheduled workflow runs every at 00:00 on Sunday UTC time. 5 | on: 6 | push: 7 | schedule: 8 | - cron: '0 0 * * 0' 9 | 10 | jobs: 11 | tests: 12 | runs-on: ubuntu-latest 13 | env: 14 | GO111MODULE: on 15 | steps: 16 | - name: Checkout Source 17 | uses: actions/checkout@v3 18 | - name: Run Gosec Security Scanner 19 | uses: securego/gosec@master 20 | with: 21 | # we let the report trigger content trigger a failure using the GitHub Security features. 22 | args: '-no-fail -fmt sarif -out results.sarif ./...' 23 | - name: Upload SARIF file 24 | uses: github/codeql-action/upload-sarif@v1 25 | with: 26 | # Path to SARIF file relative to the root of the repository 27 | sarif_file: results.sarif 28 | -------------------------------------------------------------------------------- /application/util.student_rcid.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/middleware" 8 | "github.com/spo-iitk/ras-backend/rc" 9 | "github.com/spo-iitk/ras-backend/util" 10 | ) 11 | 12 | func extractStudentRCID(ctx *gin.Context) (uint, error) { 13 | rid_string := ctx.Param("rid") 14 | rid, err := util.ParseUint(rid_string) 15 | if err != nil { 16 | return 0, err 17 | } 18 | 19 | if !rc.IsRCActive(ctx, rid) { 20 | return 0, errors.New("recruitment cycle is not active") 21 | } 22 | 23 | user_email := middleware.GetUserID(ctx) 24 | if user_email == "" { 25 | return 0, errors.New("unauthorized") 26 | } 27 | 28 | studentrcid, err := rc.FetchStudentRCID(ctx, rid, user_email) 29 | if err != nil { 30 | return 0, err 31 | } 32 | 33 | if studentrcid == 0 { 34 | return 0, errors.New("RCID not found") 35 | } 36 | 37 | return studentrcid, nil 38 | } 39 | -------------------------------------------------------------------------------- /cmd/student.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spf13/viper" 8 | "github.com/spo-iitk/ras-backend/application" 9 | "github.com/spo-iitk/ras-backend/mail" 10 | "github.com/spo-iitk/ras-backend/middleware" 11 | "github.com/spo-iitk/ras-backend/rc" 12 | "github.com/spo-iitk/ras-backend/student" 13 | ) 14 | 15 | func studentServer(mail_channel chan mail.Mail) *http.Server { 16 | PORT := viper.GetString("PORT.STUDENT") 17 | engine := gin.New() 18 | engine.Use(middleware.CORS()) 19 | engine.Use(middleware.Authenticator()) 20 | engine.Use(gin.CustomRecovery(recoveryHandler)) 21 | engine.Use(gin.Logger()) 22 | 23 | student.StudentRouter(engine) 24 | rc.StudentRouter(engine) 25 | application.StudentRouter(mail_channel, engine) 26 | 27 | server := &http.Server{ 28 | Addr: ":" + PORT, 29 | Handler: engine, 30 | ReadTimeout: readTimeout, 31 | WriteTimeout: writeTimeout, 32 | } 33 | 34 | return server 35 | } 36 | -------------------------------------------------------------------------------- /container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18-bullseye 2 | 3 | # Set the Current Working Directory inside the container 4 | WORKDIR $GOPATH/src/github.com/spo-iitk/ras-backend 5 | 6 | RUN apt-get update 7 | RUN apt-get install -y vim nginx git 8 | # Set timezone to Asia/Kolkata 9 | ENV TZ=Asia/Kolkata 10 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 11 | 12 | RUN git config --global user.name "SPO Web Team" 13 | RUN git config --global user.email "pas@iitk.ac.in" 14 | 15 | RUN git clone https://github.com/spo-iitk/ras-backend.git . 16 | 17 | RUN cp $GOPATH/src/github.com/spo-iitk/ras-backend/secret.yml.template $GOPATH/src/github.com/spo-iitk/ras-backend/secret.yml 18 | 19 | # Configure nginx 20 | RUN rm /etc/nginx/sites-enabled/default 21 | RUN ln -s $GOPATH/src/github.com/spo-iitk/ras-backend/container/nginx.conf /etc/nginx/sites-enabled/default 22 | 23 | # This container exposes port 80 to the outside world 24 | EXPOSE 80 25 | 26 | # Run the executable 27 | CMD ["./scripts/production.sh"] 28 | -------------------------------------------------------------------------------- /rc/db.notice.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func fetchAllNotices(ctx *gin.Context, rid string, notices *[]Notice) error { 10 | tx := db.WithContext(ctx).Where("recruitment_cycle_id = ?", rid).Order("created_at desc").Find(notices) 11 | return tx.Error 12 | } 13 | 14 | func createNotice(ctx *gin.Context, notice *Notice) error { 15 | tx := db.WithContext(ctx).Create(notice) 16 | return tx.Error 17 | } 18 | 19 | func removeNotice(ctx *gin.Context, nid string) error { 20 | tx := db.WithContext(ctx).Where("id = ?", nid).Delete(&Notice{}) 21 | if tx.RowsAffected == 0 { 22 | return errors.New("no notice found") 23 | } 24 | return tx.Error 25 | } 26 | 27 | func updateNotice(ctx *gin.Context, notice *Notice) error { 28 | tx := db.WithContext(ctx).Where("id = ?", notice.ID).Updates(notice) 29 | return tx.Error 30 | } 31 | 32 | func fetchNotice(ctx *gin.Context, nid string, notice *Notice) error { 33 | tx := db.WithContext(ctx).Where("id = ?", nid).First(notice) 34 | return tx.Error 35 | } 36 | -------------------------------------------------------------------------------- /container/nginx.conf: -------------------------------------------------------------------------------- 1 | server{ 2 | listen 80 default_server; 3 | server_name _; 4 | 5 | proxy_set_header Host $host; 6 | proxy_set_header X-Forwarded-For $remote_addr; 7 | 8 | location / { 9 | proxy_pass http://localhost:3470; 10 | } 11 | location /api/ras { 12 | proxy_pass http://localhost:3470; 13 | } 14 | location /api/auth { 15 | proxy_pass http://localhost:3475; 16 | } 17 | location /api/student { 18 | proxy_pass http://localhost:3480; 19 | } 20 | location /api/company { 21 | proxy_pass http://localhost:3485; 22 | } 23 | location /api/admin/rc { 24 | proxy_pass http://localhost:3490; 25 | } 26 | location /api/admin/application/rc { 27 | proxy_pass http://localhost:3492; 28 | } 29 | location /api/admin/company { 30 | proxy_pass http://localhost:3495; 31 | } 32 | location /api/admin/student { 33 | proxy_pass http://localhost:3500; 34 | } 35 | location /api/verification { 36 | proxy_pass http://localhost:3505; 37 | } 38 | } -------------------------------------------------------------------------------- /rc/student.rc.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/middleware" 8 | "github.com/spo-iitk/ras-backend/util" 9 | ) 10 | 11 | func getStudentRCHandler(ctx *gin.Context) { 12 | email := middleware.GetUserID(ctx) 13 | 14 | var rcs []RecruitmentCycle 15 | err := fetchRCsByStudent(ctx, email, &rcs) 16 | if err != nil { 17 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 18 | return 19 | } 20 | 21 | ctx.JSON(http.StatusOK, rcs) 22 | } 23 | 24 | func studentWhoamiHandler(ctx *gin.Context) { 25 | rid, err := util.ParseUint(ctx.Param("rid")) 26 | if err != nil { 27 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 28 | return 29 | } 30 | 31 | email := middleware.GetUserID(ctx) 32 | var student StudentRecruitmentCycle 33 | 34 | err = fetchStudentByEmailAndRC(ctx, email, rid, &student) 35 | if err != nil { 36 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 37 | return 38 | } 39 | 40 | ctx.JSON(http.StatusOK, student) 41 | } 42 | -------------------------------------------------------------------------------- /auth/router.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/spo-iitk/ras-backend/mail" 6 | ) 7 | 8 | func Router(mail_channel chan mail.Mail, r *gin.Engine) { 9 | auth := r.Group("/api/auth") 10 | { 11 | auth.POST("/login", loginHandler) 12 | auth.GET("/admins", getAllAdminDetailsHandler) 13 | auth.GET("/admins/:userID", getAdminDetailsHandler) 14 | auth.PUT("/admins/:userID/role", updateUserRole) 15 | auth.PUT("/admins/:userID/active", updateUserActiveStatus) 16 | auth.POST("/signup", signUpHandler(mail_channel)) 17 | auth.POST("/otp", otpHandler(mail_channel)) 18 | auth.POST("/reset-password", resetPasswordHandler(mail_channel)) 19 | auth.POST("/company-signup", companySignUpHandler(mail_channel)) 20 | 21 | auth.GET("/whoami", whoamiHandler) // who am i, if not exploited 22 | auth.GET("/credits", creditsHandler) 23 | 24 | auth.POST("/hr-signup", hrSignUpHandler(mail_channel)) 25 | 26 | auth.GET("/new-companies", companiesAddedHandler) 27 | 28 | auth.POST("/god/signup", godSignUpHandler(mail_channel)) 29 | auth.POST("/god/login", godLoginHandler) 30 | auth.POST("/god/reset-password", godResetPasswordHandler(mail_channel)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /company/db.hr.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func getAllHRUserDB(ctx *gin.Context, HRs *[]CompanyHR) error { 6 | tx := db.WithContext(ctx).Select("ID","CreatedAt","UpdatedAt","DeletedAt","Name","Email","Phone").Find(HRs) 7 | return tx.Error 8 | } 9 | 10 | func getAllHR(ctx *gin.Context, HRs *[]CompanyHR, cid uint) error { 11 | tx := db.WithContext(ctx).Where("company_id = ?", cid).Find(HRs) 12 | return tx.Error 13 | } 14 | 15 | func addHR(ctx *gin.Context, HR *CompanyHR) error { 16 | tx := db.WithContext(ctx).Create(HR) 17 | return tx.Error 18 | } 19 | 20 | func deleteHR(ctx *gin.Context, id uint) error { 21 | tx := db.WithContext(ctx).Delete(&CompanyHR{}, "id = ?", id) 22 | return tx.Error 23 | } 24 | 25 | // func updateHR(ctx *gin.Context, cid uint, hrid string, req *updateHRRequest) error { 26 | // tx := db.WithContext(ctx).Model(&CompanyHR{}).Where("company_id = ? AND email = ?", cid, hrid).Updates(req) 27 | // return tx.Error 28 | // } 29 | 30 | func FetchCompanyIDByEmail(ctx *gin.Context, email string) (uint, error) { 31 | var hr CompanyHR 32 | tx := db.WithContext(ctx).Where("email = ?", email).First(&hr) 33 | return hr.CompanyID, tx.Error 34 | } 35 | -------------------------------------------------------------------------------- /company/config.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "github.com/spf13/viper" 6 | "gorm.io/driver/postgres" 7 | "gorm.io/gorm" 8 | "gorm.io/gorm/logger" 9 | ) 10 | 11 | var db *gorm.DB 12 | 13 | func openConnection() { 14 | host := viper.GetString("DATABASE.HOST") 15 | port := viper.GetString("DATABASE.PORT") 16 | password := viper.GetString("DATABASE.PASSWORD") 17 | 18 | dbName := viper.GetString("DBNAME.COMPANY") 19 | user := dbName + viper.GetString("DATABASE.USER") 20 | 21 | dsn := "host=" + host + " user=" + user + " password=" + password 22 | dsn += " dbname=" + dbName + " port=" + port + " sslmode=disable TimeZone=Asia/Kolkata" 23 | 24 | database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ 25 | Logger: logger.Default.LogMode(logger.Info), 26 | }) 27 | if err != nil { 28 | logrus.Fatal("Failed to connect to company database: ", err) 29 | panic(err) 30 | } 31 | 32 | db = database 33 | 34 | err = db.AutoMigrate(&Company{}, &CompanyHR{}) 35 | if err != nil { 36 | logrus.Fatal("Failed to migrate company database: ", err) 37 | panic(err) 38 | } 39 | 40 | logrus.Info("Connected to company database") 41 | } 42 | 43 | func init() { 44 | openConnection() 45 | } 46 | -------------------------------------------------------------------------------- /company/router.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/spo-iitk/ras-backend/ras" 6 | ) 7 | 8 | func AdminRouter(r *gin.Engine) { 9 | admin := r.Group("/api/admin/company") 10 | { 11 | admin.GET("", getAllCompaniesHandler) 12 | admin.GET("/:cid", getCompanyHandler) 13 | admin.GET("/limited", getLimitedCompaniesHandler) 14 | 15 | admin.PUT("", updateCompanyHandler) 16 | admin.POST("", addNewHandler) 17 | admin.POST("/bulk", addNewBulkHandler) 18 | 19 | admin.DELETE("/:cid", deleteCompanyHandler) 20 | 21 | admin.GET("/hr", getAllCompanyHRsHandler) 22 | admin.GET("/:cid/hr", getAllHRHandler) 23 | admin.POST("/hr", addHRHandler) 24 | admin.DELETE("/hr/:hrid", deleteHRHandler) 25 | 26 | admin.GET("/:cid/past-hires", ras.PlaceHolderController) 27 | admin.GET("/:cid/history", ras.PlaceHolderController) 28 | admin.PUT("/:cid/history/:hid", ras.PlaceHolderController) 29 | admin.DELETE("/:cid/history/:hid", ras.PlaceHolderController) 30 | } 31 | } 32 | 33 | func CompanyRouter(r *gin.Engine) { 34 | company := r.Group("/api/company") 35 | { 36 | company.GET("/hr", getCompanyHRHandler) 37 | company.POST("/hr", postNewHRHandler) 38 | // company.PUT("/hr", putHRHandler) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /auth/model.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "github.com/spo-iitk/ras-backend/constants" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type User struct { 9 | gorm.Model 10 | UserID string `gorm:"uniqueIndex" json:"user_id"` 11 | Password string `json:"password"` 12 | RoleID constants.Role `json:"role_id" gorm:"default:1"` // student role by default 13 | Name string `json:"name"` 14 | IsActive bool `json:"is_active" gorm:"default:true"` 15 | LastLogin uint `json:"last_login" gorm:"index;autoUpdateTime:milli"` 16 | RefreshToken string `json:"refresh_token"` 17 | } 18 | 19 | type OTP struct { 20 | gorm.Model 21 | UserID string `gorm:"column:user_id"` 22 | OTP string `gorm:"column:otp"` 23 | Expires uint `gorm:"column:expires"` 24 | } 25 | 26 | type CompanySignUpRequest struct { 27 | gorm.Model 28 | CompanyName string `json:"company_name" binding:"required"` 29 | Name string `json:"name" binding:"required"` 30 | Designation string `json:"designation" binding:"required"` 31 | Email string `json:"email" binding:"required"` 32 | Phone string `json:"phone" binding:"required"` 33 | IsReviewed bool `json:"is_reviewed"` 34 | Comments string `json:"comments"` 35 | } 36 | -------------------------------------------------------------------------------- /application/admin.email.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/mail" 8 | "github.com/spo-iitk/ras-backend/rc" 9 | ) 10 | 11 | type proformaEmailRequest struct { 12 | EventID uint `json:"event_id"` 13 | Subject string `json:"subject"` 14 | Body string `json:"body"` 15 | } 16 | 17 | func proformaEmailHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 18 | return func(ctx *gin.Context) { 19 | var request proformaEmailRequest 20 | 21 | err := ctx.ShouldBindJSON(&request) 22 | if err != nil { 23 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 24 | return 25 | } 26 | 27 | studentRCID, err := fetchStudentRCIDByEvents(ctx, request.EventID) 28 | if err != nil { 29 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 30 | return 31 | } 32 | 33 | studentEmails, err := rc.FetchStudentEmailBySRCID(ctx, studentRCID) 34 | if err != nil { 35 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 36 | return 37 | } 38 | 39 | mail_channel <- mail.GenerateMails(studentEmails, request.Subject, request.Body) 40 | ctx.JSON(http.StatusOK, gin.H{"status": "email sent"}) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /student/config.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | 6 | "github.com/spf13/viper" 7 | _ "github.com/spo-iitk/ras-backend/config" 8 | "gorm.io/driver/postgres" 9 | "gorm.io/gorm" 10 | "gorm.io/gorm/logger" 11 | ) 12 | 13 | var db *gorm.DB 14 | 15 | func openConnection() { 16 | host := viper.GetString("DATABASE.HOST") 17 | port := viper.GetString("DATABASE.PORT") 18 | password := viper.GetString("DATABASE.PASSWORD") 19 | 20 | dbName := viper.GetString("DBNAME.STUDENT") 21 | user := dbName + viper.GetString("DATABASE.USER") 22 | 23 | dsn := "host=" + host + " user=" + user + " password=" + password 24 | dsn += " dbname=" + dbName + " port=" + port + " sslmode=disable TimeZone=Asia/Kolkata" 25 | 26 | database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ 27 | Logger: logger.Default.LogMode(logger.Info), 28 | }) 29 | if err != nil { 30 | logrus.Fatal("Failed to connect to student database: ", err) 31 | panic(err) 32 | } 33 | 34 | db = database 35 | 36 | err = db.AutoMigrate(&Student{}, &StudentDocument{}) 37 | if err != nil { 38 | logrus.Fatal("Failed to migrate student database: ", err) 39 | panic(err) 40 | } 41 | 42 | logrus.Info("Connected to student database") 43 | } 44 | 45 | func init() { 46 | openConnection() 47 | } 48 | -------------------------------------------------------------------------------- /auth/config.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sirupsen/logrus" 7 | 8 | "github.com/spf13/viper" 9 | _ "github.com/spo-iitk/ras-backend/config" 10 | "gorm.io/driver/postgres" 11 | "gorm.io/gorm" 12 | ) 13 | 14 | var db *gorm.DB 15 | var logf *os.File 16 | 17 | func openConnection() { 18 | host := viper.GetString("DATABASE.HOST") 19 | port := viper.GetString("DATABASE.PORT") 20 | password := viper.GetString("DATABASE.PASSWORD") 21 | 22 | dbName := viper.GetString("DBNAME.AUTH") 23 | user := dbName + viper.GetString("DATABASE.USER") 24 | 25 | dsn := "host=" + host + " user=" + user + " password=" + password 26 | dsn += " dbname=" + dbName + " port=" + port + " sslmode=disable TimeZone=Asia/Kolkata" 27 | 28 | database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ 29 | // Logger: logger.Default.LogMode(logger.Error), 30 | }) 31 | if err != nil { 32 | logrus.Fatal("Failed to connect to auth database: ", err) 33 | panic(err) 34 | } 35 | 36 | db = database 37 | 38 | err = db.AutoMigrate(&User{}, &OTP{}, &CompanySignUpRequest{}) 39 | if err != nil { 40 | logrus.Fatal("Failed to migrate auth database: ", err) 41 | panic(err) 42 | } 43 | 44 | logrus.Info("Connected to auth database") 45 | } 46 | 47 | func init() { 48 | openConnection() 49 | logf = file() 50 | go cleanupOTP() 51 | } 52 | -------------------------------------------------------------------------------- /application/util.company.middleware.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/rc" 8 | "github.com/spo-iitk/ras-backend/util" 9 | ) 10 | 11 | func ensureCompany() gin.HandlerFunc { 12 | return func(ctx *gin.Context) { 13 | rid, err := util.ParseUint(ctx.Param("rid")) 14 | if err != nil { 15 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 16 | return 17 | } 18 | 19 | if !rc.IsRCActive(ctx, rid) { 20 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "RC not active"}) 21 | return 22 | } 23 | 24 | companyID, err := extractCompanyID(ctx) 25 | if err != nil { 26 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 27 | return 28 | } 29 | 30 | ctx.Set("companyID", int(companyID)) 31 | 32 | crcid, err := rc.FetchCompanyRCID(ctx, rid, companyID) 33 | if err != nil { 34 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 35 | return 36 | } 37 | 38 | ctx.Set("companyRCID", int(crcid)) 39 | 40 | ctx.Next() 41 | } 42 | } 43 | 44 | // func getCompanyID(ctx *gin.Context) uint { 45 | // return uint(ctx.GetInt("companyID")) 46 | // } 47 | 48 | func getCompanyRCID(ctx *gin.Context) uint { 49 | return uint(ctx.GetInt("companyRCID")) 50 | } 51 | -------------------------------------------------------------------------------- /cmd/panic_alert.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/sirupsen/logrus" 11 | 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | type alertMsg struct { 16 | Endpoint string `json:"endpoint"` 17 | Err interface{} `json:"error"` 18 | } 19 | 20 | var alertChannel chan alertMsg 21 | 22 | var unix_socket = "/tmp/ras-backend.sock" 23 | 24 | func sendAlertToDiscord() { 25 | conn, err := net.Dial("unix", unix_socket) 26 | for err != nil { 27 | // logrus.Error("Error in connecting to socket: ", err) 28 | conn, err = net.Dial("unix", unix_socket) 29 | time.Sleep(5 * time.Second) 30 | } 31 | defer conn.Close() 32 | log.Println("Ready to send panic alerts") 33 | for { 34 | alert := <-alertChannel 35 | jsonData, err := json.Marshal(alert) 36 | if err != nil { 37 | logrus.Error("Error in alerting panic: ", err) 38 | continue 39 | } 40 | _, err = conn.Write(jsonData) 41 | if err != nil { 42 | logrus.Error("Error in writing data to socket: ", err) 43 | } 44 | } 45 | } 46 | 47 | func recoveryHandler(c *gin.Context, err interface{}) { 48 | alertChannel <- alertMsg{c.Request.URL.Path, err} 49 | c.AbortWithStatus(http.StatusInternalServerError) 50 | } 51 | 52 | func init() { 53 | alertChannel = make(chan alertMsg) 54 | go sendAlertToDiscord() 55 | } 56 | -------------------------------------------------------------------------------- /student/student.update.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | ) 10 | 11 | func updateStudentHandler(ctx *gin.Context) { 12 | var updateStudentRequest Student 13 | 14 | if err := ctx.ShouldBindJSON(&updateStudentRequest); err != nil { 15 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 16 | return 17 | } 18 | 19 | email := middleware.GetUserID(ctx) 20 | 21 | // if updateStudentRequest.SecondaryProgramDepartmentID > updateStudentRequest.ProgramDepartmentID && updateStudentRequest.ProgramDepartmentID != 0 { 22 | // ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Secondary program department and primary program department seems to be interchanged"}) 23 | // return 24 | // } 25 | 26 | updated, err := updateStudentByEmail(ctx, &updateStudentRequest, email) 27 | if err != nil { 28 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 29 | return 30 | } 31 | 32 | if !updated { 33 | ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "Student not found or forbidden"}) 34 | return 35 | } 36 | 37 | logrus.Infof("A student with email %s is updated", email) 38 | 39 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully updated"}) 40 | } 41 | -------------------------------------------------------------------------------- /student/router.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/spo-iitk/ras-backend/mail" 6 | "github.com/spo-iitk/ras-backend/ras" 7 | ) 8 | 9 | func StudentRouter(r *gin.Engine) { 10 | student := r.Group("/api/student") 11 | { 12 | student.PUT("", updateStudentHandler) 13 | student.GET("", getStudentHandler) 14 | student.POST("/document", postStudentDocumentHandler) 15 | student.GET("/documents", getStudentDocumentHandler) 16 | } 17 | } 18 | 19 | func AdminRouter(mail_channel chan mail.Mail, r *gin.Engine) { 20 | admin := r.Group("/api/admin/student") 21 | { 22 | admin.DELETE("/:sid", deleteStudentHandler) 23 | admin.GET("", getAllStudentsHandler) 24 | admin.GET("/limited", getLimitedStudentsHandler) 25 | admin.PUT("", updateStudentByIDHandler) 26 | admin.GET("/:sid", getStudentByIDHandler) 27 | admin.PUT("/:sid/editable",makeStudentEdiatableHandler) 28 | admin.PUT("/:sid/verify", verifyStudentHandler) 29 | admin.GET("/:sid/history", ras.PlaceHolderController) 30 | 31 | admin.POST("/:sid/clarification", postClarificationHandler(mail_channel)) 32 | admin.GET("/:sid/documents", getDocumentHandler) 33 | admin.PUT("/document/:docid/verify", putDocumentVerifyHandler(mail_channel)) 34 | admin.GET("/documents", getAllDocumentHandler) 35 | admin.GET("/documents/type/:type", getAllDocumentHandlerByType) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /application/student.events.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/util" 8 | ) 9 | 10 | type proformaEventStudentResponse struct { 11 | ProformaEvent 12 | CompanyName string `json:"company_name"` 13 | Role string `json:"role"` 14 | Profile string `json:"profile"` 15 | } 16 | 17 | func getEventsByStudentHandler(ctx *gin.Context) { 18 | rid, err := util.ParseUint(ctx.Param("rid")) 19 | if err != nil { 20 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 21 | return 22 | } 23 | 24 | var events []proformaEventStudentResponse 25 | err = fetchEventsByStudent(ctx, rid, &events) 26 | 27 | if err != nil { 28 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 29 | return 30 | } 31 | 32 | ctx.JSON(http.StatusOK, events) 33 | } 34 | 35 | func getEventsByProformaForStudentHandler(ctx *gin.Context) { 36 | pid, err := util.ParseUint(ctx.Param("pid")) 37 | if err != nil { 38 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 39 | return 40 | } 41 | 42 | var events []ProformaEvent 43 | err = fetchEventsByProforma(ctx, pid, &events) 44 | 45 | if err != nil { 46 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 47 | return 48 | } 49 | 50 | ctx.JSON(http.StatusOK, events) 51 | } 52 | -------------------------------------------------------------------------------- /student/db.document.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gorm.io/gorm/clause" 6 | ) 7 | 8 | 9 | func saveDocument(ctx *gin.Context, document *StudentDocument) error { 10 | tx := db.WithContext(ctx).Save(document) 11 | return tx.Error 12 | } 13 | 14 | func getDocumentsByStudentID(ctx *gin.Context, documents *[]StudentDocument, studentID uint) error { 15 | tx := db.WithContext(ctx).Where("student_id = ?", studentID).Find(documents) 16 | return tx.Error 17 | } 18 | 19 | func getDocumentByID(ctx *gin.Context, document *StudentDocument, docID uint) error { 20 | tx := db.WithContext(ctx).First(document, docID) 21 | return tx.Error 22 | } 23 | 24 | func getAllDocuments(ctx *gin.Context, documents *[]StudentDocument) error { 25 | tx := db.WithContext(ctx).Find(documents) 26 | return tx.Error 27 | } 28 | 29 | func getDocumentsByType(ctx *gin.Context, documents *[]StudentDocument, docType string) error { 30 | tx := db.WithContext(ctx).Where("type = ?", docType).Find(documents) 31 | return tx.Error 32 | } 33 | 34 | func updateDocumentVerify(ctx *gin.Context, docid uint, verified bool, user string) (bool, error){ 35 | var document StudentDocument 36 | tx := db.WithContext(ctx).Model(&document).Clauses(clause.Returning{}).Where("id = ?", docid).Updates(map[string]interface{}{"verified": verified, "action_taken_by": user}) 37 | return tx.RowsAffected == 1, tx.Error 38 | } -------------------------------------------------------------------------------- /rc/config.go: -------------------------------------------------------------------------------- 1 | package rc // will be reanamed later 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "github.com/spf13/viper" 6 | "gorm.io/driver/postgres" 7 | "gorm.io/gorm" 8 | "gorm.io/gorm/logger" 9 | ) 10 | 11 | var db *gorm.DB 12 | 13 | func openConnection() { 14 | host := viper.GetString("DATABASE.HOST") 15 | port := viper.GetString("DATABASE.PORT") 16 | password := viper.GetString("DATABASE.PASSWORD") 17 | 18 | dbName := viper.GetString("DBNAME.RC") 19 | user := dbName + viper.GetString("DATABASE.USER") 20 | 21 | dsn := "host=" + host + " user=" + user + " password=" + password 22 | dsn += " dbname=" + dbName + " port=" + port + " sslmode=disable TimeZone=Asia/Kolkata" 23 | 24 | database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ 25 | Logger: logger.Default.LogMode(logger.Info), 26 | }) 27 | if err != nil { 28 | logrus.Fatal("Failed to connect to cycle database: ", err) 29 | panic(err) 30 | } 31 | 32 | db = database 33 | 34 | err = db.AutoMigrate(&RecruitmentCycle{}, &RecruitmentCycleQuestion{}, 35 | &RecruitmentCycleQuestionsAnswer{}, &CompanyRecruitmentCycle{}, &Notice{}, 36 | &StudentRecruitmentCycle{}, &StudentRecruitmentCycleResume{}) 37 | if err != nil { 38 | logrus.Fatal("Failed to migrate cycle database: ", err) 39 | panic(err) 40 | } 41 | 42 | logrus.Info("Connected to cycle database") 43 | } 44 | 45 | func init() { 46 | openConnection() 47 | } 48 | -------------------------------------------------------------------------------- /company/company.hr.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func postNewHRHandler(ctx *gin.Context) { 10 | var addHRRequest CompanyHR 11 | 12 | err := ctx.ShouldBindJSON(&addHRRequest) 13 | if err != nil { 14 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 15 | return 16 | } 17 | 18 | if addHRRequest.CompanyID != 0 { 19 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Company ID is not allowed"}) 20 | return 21 | } 22 | 23 | addHRRequest.CompanyID, err = extractCompanyID(ctx) 24 | if err != nil { 25 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 26 | return 27 | } 28 | 29 | err = addHR(ctx, &addHRRequest) 30 | if err != nil { 31 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 32 | return 33 | } 34 | 35 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully added"}) 36 | 37 | } 38 | 39 | func getCompanyHRHandler(ctx *gin.Context) { 40 | var HRs []CompanyHR 41 | 42 | cid, err := extractCompanyID(ctx) 43 | if err != nil { 44 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 45 | return 46 | } 47 | 48 | err = getAllHR(ctx, &HRs, cid) 49 | 50 | if err != nil { 51 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 52 | return 53 | } 54 | 55 | ctx.JSON(http.StatusOK, HRs) 56 | } 57 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/gin-gonic/gin" 8 | _ "github.com/spo-iitk/ras-backend/config" 9 | "github.com/spo-iitk/ras-backend/mail" 10 | "golang.org/x/sync/errgroup" 11 | ) 12 | 13 | const ( 14 | readTimeout = 5 * time.Second 15 | writeTimeout = 10 * time.Second 16 | ) 17 | 18 | func main() { 19 | var g errgroup.Group 20 | mail_channel := make(chan mail.Mail) 21 | 22 | gin.SetMode(gin.ReleaseMode) 23 | 24 | go mail.Service(mail_channel) 25 | 26 | g.Go(func() error { 27 | return authServer(mail_channel).ListenAndServe() 28 | }) 29 | 30 | g.Go(func() error { 31 | return rasServer(mail_channel).ListenAndServe() 32 | }) 33 | 34 | g.Go(func() error { 35 | return studentServer(mail_channel).ListenAndServe() 36 | }) 37 | 38 | g.Go(func() error { 39 | return companyServer().ListenAndServe() 40 | }) 41 | 42 | g.Go(func() error { 43 | return adminRCServer(mail_channel).ListenAndServe() 44 | }) 45 | 46 | g.Go(func() error { 47 | return adminApplicationServer(mail_channel).ListenAndServe() 48 | }) 49 | 50 | g.Go(func() error { 51 | return adminStudentServer(mail_channel).ListenAndServe() 52 | }) 53 | 54 | g.Go(func() error { 55 | return adminCompanyServer().ListenAndServe() 56 | }) 57 | g.Go(func() error { 58 | return verificationServer(mail_channel).ListenAndServe() 59 | }) 60 | 61 | log.Println("Starting Server...") 62 | if err := g.Wait(); err != nil { 63 | log.Fatal(err) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rc/db.question.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func fetchStudentQuestions(ctx *gin.Context, rid string, questions *[]RecruitmentCycleQuestion) error { 6 | tx := db.WithContext(ctx).Where("recruitment_cycle_id = ?", rid).Find(questions) 7 | return tx.Error 8 | } 9 | 10 | func createStudentQuestion(ctx *gin.Context, question *RecruitmentCycleQuestion) error { 11 | tx := db.WithContext(ctx).Create(question) 12 | return tx.Error 13 | } 14 | 15 | func updateStudentQuestion(ctx *gin.Context, question *RecruitmentCycleQuestion) (bool, error) { 16 | tx := db.WithContext(ctx).Where("id =?", question.ID).Updates(question) 17 | return tx.RowsAffected > 0, tx.Error 18 | } 19 | 20 | func deleteStudentQuestion(ctx *gin.Context, qid string) error { 21 | tx := db.WithContext(ctx).Where("id = ?", qid).Delete(&RecruitmentCycleQuestion{}) 22 | return tx.Error 23 | } 24 | 25 | func fetchStudentQuestionsAnswers(ctx *gin.Context, rid, sid uint, questions *[]getStudentEnrollmentResponse) error { 26 | tx := db.WithContext(ctx).Model(&RecruitmentCycleQuestion{}). 27 | Joins("LEFT JOIN recruitment_cycle_questions_answers ON recruitment_cycle_questions_answers.recruitment_cycle_question_id = recruitment_cycle_questions.id AND recruitment_cycle_questions_answers.student_recruitment_cycle_id = ?", sid). 28 | Select("recruitment_cycle_questions.*, recruitment_cycle_questions_answers.answer"). 29 | Where("recruitment_cycle_questions.recruitment_cycle_id = ?", rid). 30 | Find(questions) 31 | return tx.Error 32 | } 33 | -------------------------------------------------------------------------------- /auth/user.login.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/middleware" 8 | ) 9 | 10 | type loginRequest struct { 11 | UserID string `json:"user_id" binding:"required"` 12 | Password string `json:"password" binding:"required"` 13 | RememberMe bool `json:"remember_me"` 14 | } 15 | 16 | func loginHandler(c *gin.Context) { 17 | var loginReq loginRequest 18 | if err := c.ShouldBindJSON(&loginReq); err != nil { 19 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 20 | return 21 | } 22 | 23 | hashedPwd, role, isActive, err := getPasswordAndRole(c, loginReq.UserID) 24 | if err != nil { 25 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 26 | return 27 | } 28 | 29 | if !comparePasswords(hashedPwd, loginReq.Password) { 30 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Credentials"}) 31 | return 32 | } 33 | 34 | if !isActive { 35 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "User is not active"}) 36 | return 37 | } 38 | 39 | token, err := middleware.GenerateToken(loginReq.UserID, uint(role), bool(loginReq.RememberMe)) 40 | if err != nil { 41 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 42 | return 43 | } 44 | 45 | // @1-Harshit and Copilot ❤️ 46 | //nolint:all 47 | go setLastLogin(loginReq.UserID) 48 | 49 | c.JSON(http.StatusOK, gin.H{"role_id": role, "user_id": loginReq.UserID, "token": token}) 50 | } 51 | -------------------------------------------------------------------------------- /company/db.company.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func getAllCompanies(ctx *gin.Context, companies *[]Company) error { 8 | tx := db.WithContext(ctx).Find(companies) 9 | return tx.Error 10 | } 11 | 12 | func getCompany(ctx *gin.Context, company *Company, id uint) error { 13 | tx := db.WithContext(ctx).Where("id = ?", id).First(company) 14 | return tx.Error 15 | } 16 | 17 | func getLimitedCompanies(ctx *gin.Context, companies *[]Company, lastFetchedId uint, pageSize int) error { 18 | tx := db.WithContext(ctx).Order("id asc").Where("id >= ?", lastFetchedId).Limit(pageSize).Find(companies) 19 | return tx.Error 20 | } 21 | 22 | func updateCompany(ctx *gin.Context, company *Company) (bool, error) { 23 | tx := db.WithContext(ctx).Where("id = ?", company.ID).Updates(company) 24 | return tx.RowsAffected > 0, tx.Error 25 | } 26 | 27 | func createCompany(ctx *gin.Context, company *Company) error { 28 | tx := db.WithContext(ctx).Create(company) 29 | return tx.Error 30 | } 31 | 32 | func createCompanies(ctx *gin.Context, company *[]Company) error { 33 | tx := db.WithContext(ctx).Create(company) 34 | return tx.Error 35 | } 36 | 37 | func deleteCompany(ctx *gin.Context, id uint) error { 38 | tx := db.WithContext(ctx).Where("id = ?", id).Delete(&Company{}) 39 | return tx.Error 40 | } 41 | 42 | func GetCompanyName(ctx *gin.Context, id uint) (string, error) { 43 | var c Company 44 | err := getCompany(ctx, &c, id) 45 | if err != nil { 46 | return "", err 47 | } 48 | return c.Name, nil 49 | } 50 | -------------------------------------------------------------------------------- /student/admin.clarification.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/mail" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | "github.com/spo-iitk/ras-backend/util" 10 | ) 11 | 12 | type postClarificationRequest struct { 13 | Clarification string `json:"clarification" binding:"required"` 14 | } 15 | 16 | func postClarificationHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 17 | return func(ctx *gin.Context) { 18 | sid, err := util.ParseUint(ctx.Param("sid")) 19 | if err != nil { 20 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 21 | return 22 | } 23 | 24 | var student Student 25 | err = getStudentByID(ctx, &student, sid) 26 | if err != nil { 27 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 28 | return 29 | } 30 | 31 | var request postClarificationRequest 32 | err = ctx.ShouldBindJSON(&request) 33 | if err != nil { 34 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 35 | return 36 | } 37 | 38 | mail_channel <- mail.GenerateMail(student.IITKEmail, "Asking Clarification", request.Clarification) 39 | mail_channel <- mail.GenerateMail( 40 | middleware.GetUserID(ctx), 41 | "Clarification Requested from "+student.Name, 42 | "Dear "+middleware.GetUserID(ctx)+ 43 | "Clarification was requested from "+student.Name+ 44 | "\nSent Mail:\n"+ 45 | request.Clarification) 46 | 47 | ctx.JSON(http.StatusOK, gin.H{"status": "Clarification Mail sent"}) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /auth/company.signup.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spo-iitk/ras-backend/mail" 9 | ) 10 | 11 | func companySignUpHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 12 | return func(ctx *gin.Context) { 13 | var signupReq CompanySignUpRequest 14 | 15 | err := ctx.ShouldBindJSON(&signupReq) 16 | if err != nil { 17 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 18 | return 19 | } 20 | 21 | id, err := createCompany(ctx, &signupReq) 22 | if err != nil { 23 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 24 | return 25 | } 26 | 27 | logrus.Infof("A Company %s made signUp request with id %d", signupReq.CompanyName, id) 28 | mail_channel <- mail.GenerateMail(signupReq.Email, 29 | "Registration requested on RAS", 30 | "Dear "+signupReq.Name+",\n\nWe got your request for registration on Recruitment Automation System, IIT Kanpur. We will get back to you soon. For any queries, please get in touch with us at spo@iitk.ac.in.") 31 | 32 | mail_channel <- mail.GenerateMail("spo@iitk.ac.in", 33 | "Registration requested on RAS", 34 | "Company "+signupReq.CompanyName+" has requested to be registered on RAS. The details are as follows:\n\n"+ 35 | "Name: "+signupReq.Name+"\n"+ 36 | "Designation: "+signupReq.Designation+"\n"+ 37 | "Email: "+signupReq.Email+"\n"+ 38 | "Phone: "+signupReq.Phone+"\n"+ 39 | "Comments: "+signupReq.Comments+"\n") 40 | 41 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully Requested"}) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /student/student.document.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | ) 10 | 11 | func postStudentDocumentHandler(ctx *gin.Context) { 12 | var document StudentDocument 13 | email := middleware.GetUserID(ctx) 14 | 15 | if err := ctx.ShouldBindJSON(&document); err != nil { 16 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 17 | return 18 | } 19 | var student Student 20 | err := getStudentByEmail(ctx, &student, email) 21 | if err != nil { 22 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 23 | return 24 | } 25 | 26 | document.StudentID = student.ID 27 | err = saveDocument(ctx, &document) 28 | if err != nil { 29 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 30 | return 31 | } 32 | 33 | logrus.Infof("Document for student %d uploaded", student.ID) 34 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully uploaded document"}) 35 | } 36 | 37 | func getStudentDocumentHandler(ctx *gin.Context) { 38 | email := middleware.GetUserID(ctx) 39 | var student Student 40 | err := getStudentByEmail(ctx, &student, email) 41 | if err != nil { 42 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 43 | return 44 | } 45 | 46 | var documents []StudentDocument 47 | err = getDocumentsByStudentID(ctx, &documents, student.ID) 48 | if err != nil { 49 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 50 | return 51 | } 52 | 53 | ctx.JSON(http.StatusOK, documents) 54 | } -------------------------------------------------------------------------------- /application/db.question.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func fetchProformaQuestion(ctx *gin.Context, pid uint, questions *[]ApplicationQuestion) error { 6 | tx := db.WithContext(ctx).Where("proforma_id = ?", pid).Find(&questions) 7 | return tx.Error 8 | } 9 | func fetchAllAnswers(ctx *gin.Context, pid uint, questionID []uint, answers *[]ApplicationQuestionAnswer) error { 10 | 11 | tx := db.WithContext(ctx).Where("application_question_id IN ?", questionID).Find(answers) 12 | return tx.Error 13 | } 14 | 15 | func updateProformaQuestion(ctx *gin.Context, question *ApplicationQuestion) error { 16 | tx := db.WithContext(ctx).Where("id = ?", question.ID).Updates(question) 17 | return tx.Error 18 | } 19 | 20 | func createProformaQuestion(ctx *gin.Context, question *ApplicationQuestion) error { 21 | tx := db.WithContext(ctx).Create(question) 22 | return tx.Error 23 | } 24 | 25 | func deleteProformaQuestion(ctx *gin.Context, qid uint) error { 26 | tx := db.WithContext(ctx).Where("id = ?", qid).Delete(&ApplicationQuestion{}) 27 | return tx.Error 28 | } 29 | 30 | func fetchApplicationQuestionsAnswers(ctx *gin.Context, pid, sid uint, questions *[]getApplicationResponse) error { 31 | tx := db.WithContext(ctx).Model(&ApplicationQuestion{}). 32 | Joins("LEFT JOIN application_question_answers ON application_question_answers.application_question_id = application_questions.id AND application_question_answers.student_recruitment_cycle_id = ?", sid). 33 | Select("application_questions.*, application_question_answers.answer"). 34 | Where("application_questions.proforma_id = ?", pid). 35 | Find(questions) 36 | return tx.Error 37 | } 38 | -------------------------------------------------------------------------------- /auth/util.otp.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/spf13/viper" 11 | "github.com/spo-iitk/ras-backend/mail" 12 | ) 13 | 14 | const charset = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 15 | 16 | var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) 17 | var otpExpiration = viper.GetInt("OTP.EXPIRATION") 18 | var size = viper.GetInt("OTP.SIZE") 19 | 20 | type otpRequest struct { 21 | UserID string `json:"user_id" binding:"required"` 22 | } 23 | 24 | func generateOTP() string { 25 | b := make([]byte, size) 26 | for i := range b { 27 | b[i] = charset[seededRand.Intn(len(charset))] 28 | } 29 | return string(b) 30 | } 31 | 32 | func otpHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 33 | return func(ctx *gin.Context) { 34 | var otpReq otpRequest 35 | if err := ctx.ShouldBindJSON(&otpReq); err != nil { 36 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 37 | return 38 | } 39 | 40 | otp := generateOTP() 41 | 42 | err := saveOTP(ctx, &OTP{ 43 | UserID: otpReq.UserID, 44 | OTP: otp, 45 | Expires: uint(time.Now().Add(time.Duration(otpExpiration) * time.Minute).UnixMilli()), 46 | }) 47 | if err != nil { 48 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 49 | return 50 | } 51 | mail_channel <- mail.GenerateMail(otpReq.UserID, "OTP", fmt.Sprintf("Dear %s,\n\nYour OTP is %s\nThis otp will expire in %d minutes", otpReq.UserID, otp, otpExpiration)) 52 | 53 | ctx.JSON(http.StatusOK, gin.H{"status": "OTP sent"}) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /application/config.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "github.com/spf13/viper" 6 | _ "github.com/spo-iitk/ras-backend/config" 7 | "gorm.io/driver/postgres" 8 | "gorm.io/gorm" 9 | "gorm.io/gorm/logger" 10 | ) 11 | 12 | var db *gorm.DB 13 | 14 | func openConnection() { 15 | host := viper.GetString("DATABASE.HOST") 16 | port := viper.GetString("DATABASE.PORT") 17 | password := viper.GetString("DATABASE.PASSWORD") 18 | 19 | dbName := viper.GetString("DBNAME.APPLICATION") 20 | user := dbName + viper.GetString("DATABASE.USER") 21 | 22 | dsn := "host=" + host + " user=" + user + " password=" + password 23 | dsn += " dbname=" + dbName + " port=" + port + " sslmode=disable TimeZone=Asia/Kolkata" 24 | 25 | database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ 26 | Logger: logger.Default.LogMode(logger.Info), 27 | }) 28 | if err != nil { 29 | logrus.Fatal("Failed to connect to application database: ", err) 30 | panic(err) 31 | } 32 | 33 | db = database 34 | 35 | err = db.AutoMigrate(&Proforma{}, &ApplicationQuestion{}, &ApplicationQuestionAnswer{}, 36 | &ProformaEvent{}, &EventCoordinator{}, &EventStudent{}, &ApplicationResume{}, &PVF{}) 37 | if err != nil { 38 | logrus.Fatal("Failed to migrate application database: ", err) 39 | panic(err) 40 | } 41 | 42 | logrus.Info("Connected to application database") 43 | } 44 | 45 | func init() { 46 | openConnection() 47 | gCalendarConnect() 48 | } 49 | 50 | type EventType string 51 | 52 | const ( 53 | ApplicationSubmitted EventType = "Application" 54 | Recruited EventType = "Recruited" 55 | PIOPPOACCEPTED EventType = "PIO-PPO" 56 | WALKIN EventType = "Walk-In" 57 | ) 58 | -------------------------------------------------------------------------------- /application/student.student.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/rc" 8 | "github.com/spo-iitk/ras-backend/util" 9 | ) 10 | 11 | func getStudentsByEventForStudentHandler(ctx *gin.Context) { 12 | eid, err := util.ParseUint(ctx.Param("eid")) 13 | if err != nil { 14 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 15 | return 16 | } 17 | 18 | var event ProformaEvent 19 | err = fetchEvent(ctx, eid, &event) 20 | if err != nil { 21 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 22 | return 23 | } 24 | 25 | if event.StartTime == 0 { 26 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Event has not started yet"}) 27 | return 28 | } 29 | 30 | var students []EventStudent 31 | err = fetchStudentsByEvent(ctx, eid, &students) 32 | if err != nil { 33 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 34 | return 35 | } 36 | 37 | var studentRCIDs []uint 38 | for _, student := range students { 39 | studentRCIDs = append(studentRCIDs, student.StudentRecruitmentCycleID) 40 | } 41 | 42 | var studentRCs []rc.StudentRecruitmentCycle 43 | err = rc.FetchStudents(ctx, studentRCIDs, &studentRCs) 44 | if err != nil { 45 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 46 | return 47 | } 48 | 49 | for i := range studentRCs { 50 | studentRCs[i].StudentID = 0 51 | studentRCs[i].RecruitmentCycleID = 0 52 | studentRCs[i].CPI = 0 53 | studentRCs[i].Type = "" 54 | studentRCs[i].IsFrozen = false 55 | studentRCs[i].IsVerified = false 56 | studentRCs[i].Comment = "" 57 | } 58 | 59 | ctx.JSON(http.StatusOK, studentRCs) 60 | } 61 | -------------------------------------------------------------------------------- /auth/admin.actions.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/constants" 8 | "github.com/spo-iitk/ras-backend/mail" 9 | "github.com/spo-iitk/ras-backend/middleware" 10 | ) 11 | 12 | func hrSignUpHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 13 | return func(ctx *gin.Context) { 14 | middleware.Authenticator()(ctx) 15 | if middleware.GetUserID(ctx) == "" { 16 | return 17 | } 18 | 19 | if middleware.GetRoleID(ctx) != constants.GOD && middleware.GetRoleID(ctx) != constants.OPC { 20 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Only God and OPC can sign up for HR"}) 21 | return 22 | } 23 | 24 | var request User 25 | if err := ctx.ShouldBindJSON(&request); err != nil { 26 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 27 | return 28 | } 29 | 30 | if request.Name == "" || request.Password == "" || request.UserID == "" { 31 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) 32 | return 33 | } 34 | 35 | pass := request.Password 36 | request.Password = hashAndSalt(request.Password) 37 | request.RoleID = constants.COMPANY 38 | 39 | id, err := firstOrCreateUser(ctx, &request) 40 | if err != nil { 41 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 42 | return 43 | } 44 | 45 | mail_channel <- mail.GenerateMail(request.UserID, "New Credentials generated", "Your new credentials are: \n\nUser ID: "+request.UserID+"\nPassword: "+pass+"\n\nYou can reset the password from here") 46 | ctx.JSON(http.StatusOK, gin.H{"id": id}) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /rc/student.resume.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/util" 8 | ) 9 | 10 | type ResumeRequest struct { 11 | Resume string `json:"resume"` 12 | ResumeType ResumeType `json:"resume_type"` 13 | ResumeTag string `json:"resume_tag"` 14 | } 15 | 16 | func postStudentResumeHandler(ctx *gin.Context) { 17 | rid, err := util.ParseUint(ctx.Param("rid")) 18 | if err != nil { 19 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 20 | return 21 | } 22 | 23 | var request ResumeRequest 24 | err = ctx.ShouldBindJSON(&request) 25 | if err != nil { 26 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 27 | return 28 | } 29 | 30 | sid := getStudentRCID(ctx) 31 | if sid == 0 { 32 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "SRCID not found"}) 33 | return 34 | } 35 | 36 | err = addStudentResume(ctx, request.Resume, sid, rid, request.ResumeType, request.ResumeTag) // Include resumeType in the function call 37 | if err != nil { 38 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 39 | return 40 | } 41 | 42 | ctx.JSON(http.StatusOK, gin.H{"status": "Resume Added Successfully"}) 43 | } 44 | 45 | func getStudentResumeHandler(ctx *gin.Context) { 46 | sid := getStudentRCID(ctx) 47 | if sid == 0 { 48 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "SRCID not found"}) 49 | return 50 | } 51 | 52 | var resumes []StudentRecruitmentCycleResume 53 | err := fetchStudentResume(ctx, sid, &resumes) 54 | if err != nil { 55 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 56 | return 57 | } 58 | 59 | ctx.JSON(http.StatusOK, resumes) 60 | } 61 | -------------------------------------------------------------------------------- /company/admin.HR.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func getAllHRHandler(ctx *gin.Context) { 12 | var HRs []CompanyHR 13 | 14 | cid, err := strconv.ParseUint(ctx.Param("cid"), 10, 32) 15 | if err != nil { 16 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 17 | return 18 | } 19 | 20 | err = getAllHR(ctx, &HRs, uint(cid)) 21 | 22 | if err != nil { 23 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 24 | return 25 | } 26 | 27 | ctx.JSON(http.StatusOK, HRs) 28 | } 29 | 30 | func deleteHRHandler(ctx *gin.Context) { 31 | 32 | hrid, err := strconv.ParseUint(ctx.Param("hrid"), 10, 32) 33 | if err != nil { 34 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 35 | return 36 | } 37 | 38 | err = deleteHR(ctx, uint(hrid)) 39 | if err != nil { 40 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 41 | return 42 | } 43 | 44 | logrus.Infof("An HR with id %d is deleted", hrid) 45 | 46 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully deleted"}) 47 | } 48 | 49 | func addHRHandler(ctx *gin.Context) { 50 | var addHRRequest CompanyHR 51 | 52 | if err := ctx.ShouldBindJSON(&addHRRequest); err != nil { 53 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 54 | return 55 | } 56 | 57 | err := addHR(ctx, &addHRRequest) 58 | 59 | if err != nil { 60 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 61 | return 62 | } 63 | 64 | logrus.Infof("An HR %s is added with id %d", addHRRequest.Name, addHRRequest.ID) 65 | 66 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully added"}) 67 | 68 | } 69 | -------------------------------------------------------------------------------- /company/admin.get_company.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/spo-iitk/ras-backend/util" 9 | ) 10 | 11 | func getAllCompaniesHandler(ctx *gin.Context) { 12 | var companies []Company 13 | 14 | err := getAllCompanies(ctx, &companies) 15 | 16 | if err != nil { 17 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 18 | return 19 | 20 | } 21 | ctx.JSON(http.StatusOK, companies) 22 | } 23 | 24 | func getCompanyHandler(ctx *gin.Context) { 25 | var company Company 26 | 27 | cid, err := strconv.ParseUint(ctx.Param("cid"), 10, 32) 28 | if err != nil { 29 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 30 | return 31 | } 32 | 33 | err = getCompany(ctx, &company, uint(cid)) 34 | 35 | if err != nil { 36 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 37 | return 38 | } 39 | 40 | ctx.JSON(http.StatusOK, company) 41 | } 42 | 43 | func getLimitedCompaniesHandler(ctx *gin.Context) { 44 | var companies []Company 45 | 46 | pageSize := ctx.DefaultQuery("pageSize", "100") 47 | lastFetchedId := ctx.Query("lastFetchedId") 48 | pageSizeInt, err := util.ParseUint(pageSize) 49 | if err != nil { 50 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 51 | return 52 | } 53 | lastFetchedIdInt, err := util.ParseUint(lastFetchedId) 54 | if err != nil { 55 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 56 | return 57 | } 58 | err = getLimitedCompanies(ctx, &companies, uint(lastFetchedIdInt), int(pageSizeInt)) 59 | 60 | if err != nil { 61 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 62 | return 63 | } 64 | 65 | ctx.JSON(http.StatusOK, companies) 66 | } 67 | -------------------------------------------------------------------------------- /application/company.student.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/rc" 8 | "github.com/spo-iitk/ras-backend/util" 9 | ) 10 | 11 | func getStudentsByEventForCompanyHandler(ctx *gin.Context) { 12 | cid := getCompanyRCID(ctx) 13 | if cid == 0 { 14 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "could not get company rcid"}) 15 | return 16 | } 17 | 18 | eid, err := util.ParseUint(ctx.Param("eid")) 19 | if err != nil { 20 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 21 | return 22 | } 23 | 24 | var event ProformaEvent 25 | err = fetchEvent(ctx, eid, &event) 26 | if err != nil { 27 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 28 | return 29 | } 30 | 31 | var proforma Proforma 32 | err = fetchProforma(ctx, event.ProformaID, &proforma) 33 | if err != nil { 34 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 35 | return 36 | } 37 | 38 | if proforma.CompanyRecruitmentCycleID != cid { 39 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Not authorized"}) 40 | return 41 | } 42 | 43 | students := []EventStudent{} 44 | err = fetchStudentsByEvent(ctx, eid, &students) 45 | if err != nil { 46 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 47 | return 48 | } 49 | 50 | var studentRCIDs []uint 51 | for _, student := range students { 52 | studentRCIDs = append(studentRCIDs, student.StudentRecruitmentCycleID) 53 | } 54 | 55 | var studentRCs []rc.StudentRecruitmentCycle 56 | err = rc.FetchStudents(ctx, studentRCIDs, &studentRCs) 57 | if err != nil { 58 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 59 | return 60 | } 61 | 62 | ctx.JSON(http.StatusOK, studentRCs) 63 | } 64 | -------------------------------------------------------------------------------- /auth/user.reset_password.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spo-iitk/ras-backend/mail" 9 | ) 10 | 11 | type resetPasswordRequest struct { 12 | UserID string `json:"user_id" binding:"required"` 13 | NewPassword string `json:"new_password" binding:"required"` 14 | OTP string `json:"otp" binding:"required"` 15 | } 16 | 17 | func resetPasswordHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 18 | return func(ctx *gin.Context) { 19 | var resetPasswordReq resetPasswordRequest 20 | 21 | if err := ctx.ShouldBindJSON(&resetPasswordReq); err != nil { 22 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 23 | return 24 | } 25 | 26 | verified, err := verifyOTP(ctx, resetPasswordReq.UserID, resetPasswordReq.OTP) 27 | if err != nil { 28 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 29 | return 30 | } 31 | 32 | if !verified { 33 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid OTP"}) 34 | return 35 | } 36 | 37 | hashedPwd := hashAndSalt(resetPasswordReq.NewPassword) 38 | 39 | ok, err := updatePassword(ctx, resetPasswordReq.UserID, hashedPwd) 40 | if err != nil { 41 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 42 | return 43 | } 44 | 45 | if !ok { 46 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "No such user exists"}) 47 | return 48 | } 49 | 50 | logrus.Infof("Password of %s reset successfully", resetPasswordReq.UserID) 51 | mail_channel <- mail.GenerateMail(resetPasswordReq.UserID, "Password Reset Successful", "Your password has been reset successfully.") 52 | 53 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully reset password"}) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /auth/god.reset_password.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spo-iitk/ras-backend/constants" 9 | "github.com/spo-iitk/ras-backend/mail" 10 | "github.com/spo-iitk/ras-backend/middleware" 11 | ) 12 | 13 | type godResetPasswordRequest struct { 14 | UserID string `json:"user_id" binding:"required"` 15 | NewPassword string `json:"new_password" binding:"required"` 16 | } 17 | 18 | func godResetPasswordHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 19 | return func(ctx *gin.Context) { 20 | middleware.Authenticator()(ctx) 21 | if middleware.GetRoleID(ctx) != constants.GOD && middleware.GetRoleID(ctx) != constants.OPC { 22 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Only OPC and GOD can access"}) 23 | return 24 | } 25 | 26 | var resetPasswordReq godResetPasswordRequest 27 | 28 | if err := ctx.ShouldBindJSON(&resetPasswordReq); err != nil { 29 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 30 | return 31 | } 32 | 33 | hashedPwd := hashAndSalt(resetPasswordReq.NewPassword) 34 | 35 | ok, err := updatePasswordbyGod(ctx, resetPasswordReq.UserID, hashedPwd) 36 | if err != nil { 37 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 38 | return 39 | } 40 | 41 | if !ok { 42 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "No such student exists"}) 43 | return 44 | } 45 | 46 | logrus.Infof("Password of %s reset successfully", resetPasswordReq.UserID) 47 | mail_channel <- mail.GenerateMail(resetPasswordReq.UserID, "Password Reset Successfully", "Your password has been reset successfully. Your new password is: "+resetPasswordReq.NewPassword) 48 | 49 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully reset password"}) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /auth/god.signup.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spo-iitk/ras-backend/constants" 9 | "github.com/spo-iitk/ras-backend/mail" 10 | "github.com/spo-iitk/ras-backend/middleware" 11 | ) 12 | 13 | func godSignUpHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 14 | return func(ctx *gin.Context) { 15 | middleware.Authenticator()(ctx) 16 | if middleware.GetRoleID(ctx) != constants.GOD { 17 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Only God can sign up for GOD"}) 18 | return 19 | } 20 | 21 | var req User 22 | 23 | err := ctx.ShouldBindJSON(&req) 24 | if err != nil { 25 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 26 | return 27 | } 28 | 29 | if req.UserID == "" || req.Password == "" || req.Name == "" || req.RoleID == 0 { 30 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing fields"}) 31 | return 32 | } 33 | 34 | if req.RoleID == constants.STUDENT || req.RoleID == constants.COMPANY || req.RoleID == constants.GOD { 35 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid role"}) 36 | return 37 | } 38 | 39 | pass := req.Password 40 | req.Password = hashAndSalt(req.Password) 41 | 42 | id, err := firstOrCreateUser(ctx, &req) 43 | if err != nil { 44 | ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 45 | return 46 | } 47 | 48 | mail_channel <- mail.GenerateMail(req.UserID, "Registered on RAS", "Dear "+req.Name+",\n\nYou have been registered as an Admin.\n"+"Your new credentials are: \n\nUser ID: "+req.UserID+"\nPassword: "+pass) 49 | 50 | logrus.Info("Admin registered: ", req.UserID, " by ", middleware.GetUserID(ctx), " with id ", id) 51 | ctx.JSON(http.StatusOK, gin.H{"id": id}) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /rc/admin.clarification.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/mail" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | "github.com/spo-iitk/ras-backend/util" 10 | ) 11 | 12 | type postClarificationRequest struct { 13 | Clarification string `json:"clarification" binding:"required"` 14 | } 15 | 16 | func postClarificationHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 17 | return func(ctx *gin.Context) { 18 | sid, err := util.ParseUint(ctx.Param("sid")) 19 | if err != nil { 20 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 21 | return 22 | } 23 | 24 | var student StudentRecruitmentCycle 25 | err = FetchStudent(ctx, sid, &student) 26 | if err != nil { 27 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 28 | return 29 | } 30 | 31 | rid, err := util.ParseUint(ctx.Param("rid")) 32 | if err != nil { 33 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 34 | return 35 | } 36 | 37 | if student.RecruitmentCycleID != rid { 38 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "Student does not belong to this recruitment cycle"}) 39 | return 40 | } 41 | 42 | var request postClarificationRequest 43 | err = ctx.ShouldBindJSON(&request) 44 | if err != nil { 45 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 46 | return 47 | } 48 | 49 | mail_channel <- mail.GenerateMail(student.Email, "Asking Clarification", request.Clarification) 50 | mail_channel <- mail.GenerateMail( 51 | middleware.GetUserID(ctx), 52 | "Clarification Requested from "+student.Name, 53 | "Dear "+middleware.GetUserID(ctx)+ 54 | "Clarification was requested from "+student.Name+ 55 | "\nSent Mail:\n"+ 56 | request.Clarification) 57 | 58 | ctx.JSON(http.StatusOK, gin.H{"status": "Clarification Mail sent"}) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /rc/student.enrollment.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/spo-iitk/ras-backend/util" 9 | ) 10 | 11 | type getStudentEnrollmentResponse struct { 12 | RecruitmentCycleQuestion 13 | Answer string `json:"answer"` 14 | } 15 | 16 | func getStudentEnrollmentHandler(ctx *gin.Context) { 17 | rid, err := util.ParseUint(ctx.Param("rid")) 18 | if err != nil { 19 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 20 | return 21 | } 22 | 23 | sid := getStudentRCID(ctx) 24 | if sid == 0 { 25 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "SRCID not found"}) 26 | return 27 | } 28 | 29 | var result []getStudentEnrollmentResponse 30 | 31 | err = fetchStudentQuestionsAnswers(ctx, rid, sid, &result) 32 | if err != nil { 33 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 34 | return 35 | } 36 | 37 | ctx.JSON(http.StatusOK, result) 38 | } 39 | 40 | func postEnrollmentAnswerHandler(ctx *gin.Context) { 41 | var answer RecruitmentCycleQuestionsAnswer 42 | 43 | err := ctx.ShouldBindJSON(&answer) 44 | if err != nil { 45 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 46 | return 47 | } 48 | 49 | srcid, verified, err := extractStudentRCID(ctx) 50 | if err != nil { 51 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 52 | return 53 | } 54 | 55 | if verified { 56 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Already Verified"}) 57 | return 58 | } 59 | 60 | answer.StudentRecruitmentCycleID = srcid 61 | 62 | err = createStudentAnswer(ctx, &answer) 63 | if err != nil { 64 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 65 | return 66 | } 67 | 68 | ctx.JSON(http.StatusOK, gin.H{"status": fmt.Sprintf("Answer %d created", answer.ID)}) 69 | } 70 | -------------------------------------------------------------------------------- /auth/god.login.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/constants" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | ) 10 | 11 | type godLoginRequest struct { 12 | AdminID string `json:"admin_id" binding:"required"` 13 | Password string `json:"password" binding:"required"` 14 | UserID string `json:"user_id" binding:"required"` 15 | RememberMe bool `json:"remember_me"` 16 | } 17 | 18 | func godLoginHandler(c *gin.Context) { 19 | var loginReq godLoginRequest 20 | if err := c.ShouldBindJSON(&loginReq); err != nil { 21 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 22 | return 23 | } 24 | 25 | hashedPwd, role, isActive, err := getPasswordAndRole(c, loginReq.AdminID) 26 | if err != nil { 27 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 28 | return 29 | } 30 | 31 | if !comparePasswords(hashedPwd, loginReq.Password) { 32 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Credentials"}) 33 | return 34 | } 35 | 36 | if role != constants.GOD && role != constants.OPC { 37 | c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Only God and OPC can login"}) 38 | return 39 | } 40 | 41 | if !isActive && role != constants.GOD { 42 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Admin is not active"}) 43 | return 44 | } 45 | 46 | _, role, _, err = getPasswordAndRole(c, loginReq.UserID) 47 | if err != nil { 48 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 49 | return 50 | } 51 | 52 | token, err := middleware.GenerateToken(loginReq.UserID, uint(role), bool(loginReq.RememberMe)) 53 | if err != nil { 54 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 55 | return 56 | } 57 | 58 | c.JSON(http.StatusOK, gin.H{"role_id": role, "user_id": loginReq.UserID, "token": token}) 59 | } 60 | -------------------------------------------------------------------------------- /application/student.proforma.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/rc" 8 | "github.com/spo-iitk/ras-backend/util" 9 | ) 10 | 11 | func getProformasForStudentHandler(ctx *gin.Context) { 12 | rid, err := util.ParseUint(ctx.Param("rid")) 13 | if err != nil { 14 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 15 | return 16 | } 17 | 18 | var jps []Proforma 19 | 20 | err = fetchProformasForStudent(ctx, rid, &jps) 21 | if err != nil { 22 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 23 | return 24 | } 25 | 26 | ctx.JSON(http.StatusOK, jps) 27 | } 28 | 29 | func getProformasForEligibleStudentHandler(ctx *gin.Context) { 30 | rid, err := util.ParseUint(ctx.Param("rid")) 31 | if err != nil { 32 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 33 | return 34 | } 35 | 36 | sid := getStudentRCID(ctx) 37 | var student rc.StudentRecruitmentCycle 38 | 39 | err = rc.FetchStudent(ctx, sid, &student) 40 | if err != nil { 41 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 42 | return 43 | } 44 | 45 | var jps []Proforma 46 | 47 | err = fetchProformaForEligibleStudent(ctx, rid, &student, &jps) 48 | if err != nil { 49 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 50 | return 51 | } 52 | 53 | ctx.JSON(http.StatusOK, jps) 54 | } 55 | 56 | func getProformaForStudentHandler(ctx *gin.Context) { 57 | pid, err := util.ParseUint(ctx.Param("pid")) 58 | if err != nil { 59 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 60 | return 61 | } 62 | 63 | var jp Proforma 64 | 65 | err = fetchProformaForStudent(ctx, pid, &jp) 66 | if err != nil { 67 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 68 | return 69 | } 70 | 71 | ctx.JSON(http.StatusOK, jp) 72 | } 73 | -------------------------------------------------------------------------------- /application/model.hooks.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "strings" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | func (jp *Proforma) AfterUpdate(tx *gorm.DB) (err error) { 10 | if jp.IsApproved.Valid && jp.IsApproved.Bool { 11 | event := ProformaEvent{ 12 | ProformaID: jp.ID, 13 | Name: string(Recruited), 14 | Duration: "-", 15 | StartTime: 0, 16 | EndTime: 0, 17 | Sequence: 1000, 18 | RecordAttendance: false, 19 | } 20 | 21 | err = tx.Where("proforma_id = ? AND name = ?", event.ProformaID, event.Name).FirstOrCreate(&event).Error 22 | if err != nil { 23 | return 24 | } 25 | 26 | event = ProformaEvent{ 27 | ProformaID: jp.ID, 28 | Name: string(ApplicationSubmitted), 29 | Duration: "-", 30 | StartTime: 0, 31 | EndTime: 0, 32 | Sequence: 0, 33 | RecordAttendance: false, 34 | } 35 | 36 | err = tx.Where("proforma_id = ? AND name = ?", event.ProformaID, event.Name).FirstOrCreate(&event).Error 37 | if err != nil { 38 | return 39 | } 40 | 41 | // if jp.Deadline > 0 { 42 | // go insertCalenderApplicationDeadline(jp, &event) 43 | // } 44 | } 45 | return 46 | } 47 | 48 | // Set first char of eligibility to 0 49 | func (p *Proforma) BeforeUpdate(tx *gorm.DB) (err error) { 50 | if p.Eligibility != "" { 51 | p.Eligibility = "0" + p.Eligibility[1:] 52 | } 53 | return 54 | } 55 | 56 | // Set default eligibility to none 57 | func (p *Proforma) BeforeCreate(tx *gorm.DB) (err error) { 58 | p.Eligibility = strings.Repeat("0", 130) 59 | return 60 | } 61 | 62 | // Set default options of boolean to true,false 63 | func (ques *ApplicationQuestion) BeforeCreate(tx *gorm.DB) (err error) { 64 | if ques.Type == BOOLEAN { 65 | ques.Options = "True,False" 66 | } 67 | return 68 | } 69 | func (ques *ApplicationQuestion) BeforeUpdate(tx *gorm.DB) (err error) { 70 | if ques.Type == BOOLEAN { 71 | ques.Options = "True,False" 72 | } 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /student/admin.student.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/spo-iitk/ras-backend/util" 9 | ) 10 | 11 | func getStudentByIDHandler(ctx *gin.Context) { 12 | var student Student 13 | 14 | id, err := strconv.ParseUint(ctx.Param("sid"), 10, 32) 15 | if err != nil { 16 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 17 | return 18 | } 19 | 20 | err = getStudentByID(ctx, &student, uint(id)) 21 | 22 | if err != nil { 23 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 24 | return 25 | } 26 | 27 | ctx.JSON(http.StatusOK, student) 28 | } 29 | 30 | func getAllStudentsHandler(ctx *gin.Context) { 31 | var students []Student 32 | 33 | err := getAllStudents(ctx, &students) 34 | 35 | if err != nil { 36 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 37 | return 38 | } 39 | 40 | ctx.JSON(http.StatusOK, students) 41 | } 42 | 43 | func getLimitedStudentsHandler(ctx *gin.Context) { 44 | var students []Student 45 | print(ctx.Request.URL.Query()) 46 | pageSize := ctx.DefaultQuery("pageSize", "100") 47 | lastFetchedId := ctx.Query("lastFetchedId") 48 | batch := ctx.Query("batch") 49 | 50 | pageSizeInt, err := util.ParseUint(pageSize) 51 | if err != nil { 52 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 53 | return 54 | } 55 | lastFetchedIdInt, err := util.ParseUint(lastFetchedId) 56 | if err != nil { 57 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 58 | return 59 | } 60 | batchInt, err := util.ParseUint(batch) 61 | if err != nil { 62 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 63 | return 64 | } 65 | 66 | err = getLimitedStudents(ctx, &students, lastFetchedIdInt, pageSizeInt, batchInt) 67 | 68 | if err != nil { 69 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 70 | return 71 | } 72 | 73 | ctx.JSON(http.StatusOK, students) 74 | } 75 | -------------------------------------------------------------------------------- /rc/admin.answers.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/util" 8 | ) 9 | 10 | func getStudentAnswersHandler(ctx *gin.Context) { 11 | sid, err := util.ParseUint(ctx.Param("sid")) 12 | if err != nil { 13 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 14 | return 15 | } 16 | 17 | rid, err := util.ParseUint(ctx.Param("rid")) 18 | if err != nil { 19 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 20 | return 21 | } 22 | 23 | var answers []getStudentEnrollmentResponse 24 | 25 | err = fetchStudentQuestionsAnswers(ctx, rid, sid, &answers) 26 | if err != nil { 27 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 28 | return 29 | } 30 | 31 | ctx.JSON(http.StatusOK, answers) 32 | } 33 | 34 | // func putStudentAnswer(ctx *gin.Context) { 35 | // var answer RecruitmentCycleQuestionsAnswer 36 | 37 | // err := ctx.ShouldBindJSON(&answer) 38 | // if err != nil { 39 | // ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 40 | // return 41 | // } 42 | 43 | // err = updateStudentAnswer(ctx, &answer) 44 | // if err != nil { 45 | // ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 46 | // return 47 | // } 48 | 49 | // user := middleware.GetUserID(ctx) 50 | 51 | // logrus.Infof("%v updated a student answer with id %d", user, answer.ID) 52 | 53 | // ctx.JSON(http.StatusOK, gin.H{"status": "updated student answer"}) 54 | // } 55 | 56 | // func deleteStudentAnswerHandler(ctx *gin.Context) { 57 | // sid := ctx.Param("sid") 58 | // qid := ctx.Param("qid") 59 | 60 | // err := deleteStudentAnswer(ctx, qid, sid) 61 | // if err != nil { 62 | // ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 63 | // return 64 | // } 65 | 66 | // user := middleware.GetUserID(ctx) 67 | 68 | // logrus.Infof("%v deleted a student answer with id %d", user, sid) 69 | 70 | // ctx.JSON(http.StatusOK, gin.H{"status": "deleted student answer"}) 71 | // } 72 | -------------------------------------------------------------------------------- /rc/db.rc.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func fetchAllRCs(ctx *gin.Context, rc *[]RecruitmentCycle) error { 8 | tx := db.WithContext(ctx).Order("is_active DESC, id ASC").Find(rc) 9 | return tx.Error 10 | } 11 | 12 | func fetchAllActiveRCs(ctx *gin.Context, rc *[]RecruitmentCycle) error { 13 | tx := db.WithContext(ctx).Where("is_active = ?", true).Find(rc) 14 | return tx.Error 15 | } 16 | 17 | func fetchRCsByStudent(ctx *gin.Context, email string, rcs *[]RecruitmentCycle) error { 18 | tx := db.WithContext(ctx). 19 | Joins("JOIN student_recruitment_cycles ON student_recruitment_cycles.recruitment_cycle_id = recruitment_cycles.id"). 20 | Where("student_recruitment_cycles.deleted_at IS NULL AND student_recruitment_cycles.email = ?", email). 21 | Find(&rcs) 22 | return tx.Error 23 | } 24 | 25 | func fetchRCsByCompanyID(ctx *gin.Context, cid uint, rcs *[]RecruitmentCycle) error { 26 | tx := db.WithContext(ctx). 27 | Joins("JOIN company_recruitment_cycles ON company_recruitment_cycles.recruitment_cycle_id = recruitment_cycles.id"). 28 | Where("company_recruitment_cycles.deleted_at IS NULL AND company_recruitment_cycles.company_id = ? AND recruitment_cycles.deleted_at IS NULL AND recruitment_cycles.is_active = ?", cid, true).Find(&rcs) 29 | return tx.Error 30 | } 31 | 32 | func createRC(ctx *gin.Context, rc *RecruitmentCycle) error { 33 | tx := db.WithContext(ctx).Create(rc) 34 | return tx.Error 35 | } 36 | 37 | func fetchRC(ctx *gin.Context, rid string, rc *RecruitmentCycle) error { 38 | tx := db.WithContext(ctx).Where("id = ?", rid).First(rc) 39 | return tx.Error 40 | } 41 | 42 | func IsRCActive(ctx *gin.Context, rid uint) bool { 43 | tx := db.WithContext(ctx).Where("id = ? AND is_active = ?", rid, true).First(&RecruitmentCycle{}) 44 | return tx.Error == nil 45 | } 46 | 47 | func updateRC(ctx *gin.Context, id uint, inactive bool, countcap uint) (bool, error) { 48 | tx := db.WithContext(ctx).Model(&RecruitmentCycle{}).Where("id = ?", id). 49 | Update("is_active", !inactive).Updates(&RecruitmentCycle{ApplicationCountCap: countcap}) 50 | return tx.RowsAffected == 1, tx.Error 51 | } 52 | -------------------------------------------------------------------------------- /application/db.events.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gorm.io/gorm/clause" 6 | ) 7 | 8 | func fetchEventsByRC(ctx *gin.Context, rid uint, events *[]getAllEventsByRCResponse) error { 9 | tx := db.WithContext(ctx).Model(&ProformaEvent{}). 10 | Joins("JOIN proformas ON proformas.id = proforma_events.proforma_id"). 11 | Where("proformas.deleted_at IS NULL AND proformas.recruitment_cycle_id = ?", rid). 12 | Order("start_time DESC, proforma_id, sequence"). 13 | Select("proforma_events.*, proformas.company_name, proformas.role, proformas.profile"). 14 | Find(events) 15 | return tx.Error 16 | } 17 | 18 | func fetchEvent(ctx *gin.Context, id uint, event *ProformaEvent) error { 19 | tx := db.WithContext(ctx).Where("id = ?", id).Order("sequence").First(event) 20 | return tx.Error 21 | } 22 | 23 | func fetchEventsByProforma(ctx *gin.Context, pid uint, events *[]ProformaEvent) error { 24 | tx := db.WithContext(ctx).Where("proforma_id = ?", pid).Order("sequence").Find(events) 25 | return tx.Error 26 | } 27 | 28 | func createEvent(ctx *gin.Context, event *ProformaEvent) error { 29 | tx := db.WithContext(ctx).Where("proforma_id = ? AND name = ?", event.ProformaID, event.Name).FirstOrCreate(event) 30 | return tx.Error 31 | } 32 | 33 | func updateEvent(ctx *gin.Context, event *ProformaEvent) error { 34 | tx := db.WithContext(ctx).Clauses(clause.Returning{}).Where("id = ?", event.ID).Updates(event) 35 | return tx.Error 36 | } 37 | 38 | func updateEventCalID(event *ProformaEvent) error { 39 | tx := db.Clauses(clause.Returning{}).Where("id = ?", event.ID).Updates(event) 40 | return tx.Error 41 | } 42 | 43 | func deleteEvent(ctx *gin.Context, id uint) error { 44 | tx := db.WithContext(ctx).Where("id = ?", id).Delete(&ProformaEvent{}) 45 | return tx.Error 46 | } 47 | 48 | func fetchEventsByStudent(ctx *gin.Context, rid uint, events *[]proformaEventStudentResponse) error { 49 | tx := db.WithContext(ctx). 50 | Model(&ProformaEvent{}). 51 | Joins("JOIN proformas ON proformas.id=proforma_events.proforma_id AND proformas.recruitment_cycle_id = ?", rid). 52 | Where("proforma_events.start_time > 0"). 53 | Order("start_time DESC, proforma_id, sequence"). 54 | Select("proforma_events.*, proformas.company_name, proformas.role, proformas.profile"). 55 | Find(events) 56 | return tx.Error 57 | } 58 | -------------------------------------------------------------------------------- /student/model.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type Student struct { 10 | gorm.Model 11 | RollNo string `gorm:"uniqueIndex" json:"roll_no"` 12 | Name string `json:"name"` 13 | Specialization string `json:"specialization"` 14 | Preference string `json:"preference"` 15 | Gender string `json:"gender"` 16 | Disablity string `json:"disability"` 17 | DOB uint `json:"dob"` 18 | ExpectedGraduationYear uint `json:"expected_graduation_year"` 19 | IITKEmail string `gorm:"uniqueIndex" json:"iitk_email"` 20 | PersonalEmail string `json:"personal_email"` 21 | Phone string `json:"phone"` 22 | AlternatePhone string `json:"alternate_phone"` 23 | WhatsappNumber string `json:"whatsapp_number"` 24 | ProgramDepartmentID uint `gorm:"index" json:"program_department_id"` 25 | SecondaryProgramDepartmentID uint `gorm:"index" json:"secondary_program_department_id"` 26 | CurrentCPI float64 `json:"current_cpi"` 27 | UGCPI float64 `json:"ug_cpi"` 28 | TenthBoard string `json:"tenth_board"` 29 | TenthYear uint `json:"tenth_year"` 30 | TenthMarks float64 `json:"tenth_marks"` 31 | TwelfthBoard string `json:"twelfth_board"` 32 | TwelfthYear uint `json:"twelfth_year"` 33 | TwelfthMarks float64 `json:"twelfth_marks"` 34 | EntranceExam string `json:"entrance_exam"` 35 | EntranceExamRank uint `json:"entrance_exam_rank"` 36 | Category string `json:"category"` 37 | CategoryRank uint `json:"category_rank"` 38 | CurrentAddress string `json:"current_address"` 39 | PermanentAddress string `json:"permanent_address"` 40 | FriendName string `json:"friend_name"` 41 | FriendPhone string `json:"friend_phone"` 42 | IsEditable bool `json:"is_editable" gorm:"default:true"` 43 | IsVerified bool `json:"is_verified" gorm:"default:false"` 44 | } 45 | 46 | type StudentDocument struct { 47 | gorm.Model 48 | StudentID uint `json:"sid"` 49 | Type string `json:"type"` 50 | Path string `json:"path"` 51 | Verified sql.NullBool `json:"verified" gorm:"default:NULL"` 52 | ActionTakenBy string `json:"action_taken_by"` 53 | } -------------------------------------------------------------------------------- /auth/user.signup.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spo-iitk/ras-backend/mail" 9 | "github.com/spo-iitk/ras-backend/student" 10 | ) 11 | 12 | type signUpRequest struct { 13 | UserID string `json:"user_id" binding:"required"` 14 | Name string `json:"name" binding:"required"` 15 | Password string `json:"password" binding:"required"` 16 | RollNo string `json:"roll_no" binding:"required"` 17 | UserOTP string `json:"user_otp" binding:"required"` 18 | RollNoOTP string `json:"roll_no_otp" binding:"required"` 19 | } 20 | 21 | func signUpHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 22 | return func(ctx *gin.Context) { 23 | var signupReq signUpRequest 24 | 25 | if err := ctx.ShouldBindJSON(&signupReq); err != nil { 26 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 27 | return 28 | } 29 | 30 | verified, err := verifyOTP(ctx, signupReq.UserID, signupReq.UserOTP) 31 | if err != nil { 32 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 33 | return 34 | } 35 | if !verified { 36 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid User OTP"}) 37 | return 38 | } 39 | 40 | verified, err = verifyOTP(ctx, signupReq.RollNo+"@iitk.ac.in", signupReq.RollNoOTP) 41 | if err != nil { 42 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 43 | return 44 | } 45 | if !verified { 46 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid Roll No OTP"}) 47 | return 48 | } 49 | 50 | hashedPwd := hashAndSalt(signupReq.Password) 51 | 52 | id, err := firstOrCreateUser(ctx, &User{ 53 | UserID: signupReq.UserID, 54 | Name: signupReq.Name, 55 | Password: hashedPwd, 56 | }) 57 | 58 | if err != nil { 59 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 60 | return 61 | } 62 | 63 | var createStudent = student.Student{ 64 | IITKEmail: signupReq.UserID, 65 | Name: signupReq.Name, 66 | RollNo: signupReq.RollNo, 67 | } 68 | 69 | err = student.FirstOrCreateStudent(ctx, &createStudent) 70 | 71 | if err != nil { 72 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 73 | return 74 | } 75 | 76 | logrus.Infof("User %s created successfully with id %d", signupReq.UserID, id) 77 | mail_channel <- mail.GenerateMail(signupReq.UserID, "Registered on RAS", "Dear "+signupReq.Name+",\n\nYou have been registered on RAS") 78 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully signed up"}) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /application/company.application.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/rc" 8 | "github.com/spo-iitk/ras-backend/util" 9 | ) 10 | 11 | type studentCompanysideResponse struct { 12 | ID uint `json:"id"` 13 | Name string `json:"name"` 14 | Email string `json:"email"` 15 | RollNo string `json:"roll_no"` 16 | CPI float64 `json:"cpi"` 17 | ProgramDepartmentID uint `json:"program_department_id"` 18 | SecondaryProgramDepartmentID uint `json:"secondary_program_department_id"` 19 | Resume string `json:"resume"` 20 | StatusName string `json:"status_name"` 21 | Frozen bool `json:"frozen"` 22 | } 23 | 24 | func getStudentsForCompanyByRole(ctx *gin.Context) { 25 | pid, err := util.ParseUint(ctx.Param("pid")) 26 | if err != nil { 27 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 28 | return 29 | } 30 | 31 | var applied []ApplicantsByRole 32 | err = fetchApplicantDetails(ctx, pid, &applied) 33 | if err != nil { 34 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 35 | return 36 | } 37 | 38 | var srids []uint 39 | for _, applicant := range applied { 40 | srids = append(srids, applicant.StudentRCID) 41 | } 42 | 43 | var allStudentsRC []rc.StudentRecruitmentCycle 44 | err = rc.FetchStudentBySRID(ctx, srids, &allStudentsRC) 45 | if err != nil { 46 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 47 | return 48 | } 49 | 50 | var allStudentsRCMap = make(map[uint]*rc.StudentRecruitmentCycle) 51 | for i := range allStudentsRC { 52 | allStudentsRCMap[allStudentsRC[i].ID] = &allStudentsRC[i] 53 | } 54 | 55 | var validApplicants []studentCompanysideResponse 56 | for _, s := range applied { 57 | if allStudentsRCMap[s.StudentRCID].IsFrozen { 58 | continue 59 | } 60 | 61 | applicant_details := studentCompanysideResponse{} 62 | applicant_details.ID = s.StudentRCID 63 | applicant_details.Resume = s.ResumeLink 64 | applicant_details.StatusName = s.Name 65 | 66 | studentRC := allStudentsRCMap[s.StudentRCID] 67 | 68 | applicant_details.Name = studentRC.Name 69 | applicant_details.Email = studentRC.Email 70 | applicant_details.RollNo = studentRC.RollNo 71 | applicant_details.CPI = studentRC.CPI 72 | applicant_details.ProgramDepartmentID = studentRC.ProgramDepartmentID 73 | applicant_details.SecondaryProgramDepartmentID = studentRC.SecondaryProgramDepartmentID 74 | 75 | validApplicants = append(validApplicants, applicant_details) 76 | } 77 | 78 | ctx.JSON(http.StatusOK, validApplicants) 79 | } 80 | -------------------------------------------------------------------------------- /cmd/admin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spf13/viper" 8 | "github.com/spo-iitk/ras-backend/application" 9 | "github.com/spo-iitk/ras-backend/company" 10 | "github.com/spo-iitk/ras-backend/mail" 11 | "github.com/spo-iitk/ras-backend/middleware" 12 | "github.com/spo-iitk/ras-backend/rc" 13 | "github.com/spo-iitk/ras-backend/student" 14 | ) 15 | 16 | func adminRCServer(mail_channel chan mail.Mail) *http.Server { 17 | PORT := viper.GetString("PORT.ADMIN.RC") 18 | engine := gin.New() 19 | engine.Use(middleware.CORS()) 20 | engine.Use(middleware.Authenticator()) 21 | engine.Use(middleware.EnsurePsuedoAdmin()) 22 | engine.Use(gin.CustomRecovery(recoveryHandler)) 23 | engine.Use(gin.Logger()) 24 | 25 | rc.AdminRouter(mail_channel, engine) 26 | 27 | server := &http.Server{ 28 | Addr: ":" + PORT, 29 | Handler: engine, 30 | ReadTimeout: readTimeout, 31 | WriteTimeout: writeTimeout, 32 | } 33 | 34 | return server 35 | } 36 | 37 | func adminApplicationServer(mail_channel chan mail.Mail) *http.Server { 38 | PORT := viper.GetString("PORT.ADMIN.APP") 39 | engine := gin.New() 40 | engine.Use(middleware.CORS()) 41 | engine.Use(middleware.Authenticator()) 42 | engine.Use(middleware.EnsurePsuedoAdmin()) 43 | engine.Use(gin.CustomRecovery(recoveryHandler)) 44 | engine.Use(gin.Logger()) 45 | 46 | application.AdminRouter(mail_channel, engine) 47 | 48 | server := &http.Server{ 49 | Addr: ":" + PORT, 50 | Handler: engine, 51 | ReadTimeout: readTimeout, 52 | WriteTimeout: writeTimeout, 53 | } 54 | 55 | return server 56 | } 57 | 58 | func adminCompanyServer() *http.Server { 59 | PORT := viper.GetString("PORT.ADMIN.COMPANY") 60 | engine := gin.New() 61 | engine.Use(middleware.CORS()) 62 | engine.Use(middleware.Authenticator()) 63 | engine.Use(middleware.EnsureAdmin()) 64 | engine.Use(gin.CustomRecovery(recoveryHandler)) 65 | engine.Use(gin.Logger()) 66 | 67 | company.AdminRouter(engine) 68 | 69 | server := &http.Server{ 70 | Addr: ":" + PORT, 71 | Handler: engine, 72 | ReadTimeout: readTimeout, 73 | WriteTimeout: writeTimeout, 74 | } 75 | 76 | return server 77 | } 78 | 79 | func adminStudentServer(mail_channel chan mail.Mail) *http.Server { 80 | PORT := viper.GetString("PORT.ADMIN.STUDENT") 81 | engine := gin.New() 82 | engine.Use(middleware.CORS()) 83 | engine.Use(middleware.Authenticator()) 84 | engine.Use(middleware.EnsurePsuedoAdmin()) 85 | engine.Use(gin.CustomRecovery(recoveryHandler)) 86 | 87 | student.AdminRouter(mail_channel, engine) 88 | 89 | server := &http.Server{ 90 | Addr: ":" + PORT, 91 | Handler: engine, 92 | ReadTimeout: readTimeout, 93 | WriteTimeout: writeTimeout, 94 | } 95 | 96 | return server 97 | } 98 | -------------------------------------------------------------------------------- /auth/db.user.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/constants" 8 | ) 9 | 10 | func firstOrCreateUser(ctx *gin.Context, user *User) (uint, error) { 11 | tx := db.WithContext(ctx).Create(user) 12 | if tx.Error != nil { 13 | tx = db.WithContext(ctx).Where("user_id = ?", user.UserID).Updates(user) 14 | } 15 | return user.ID, tx.Error 16 | } 17 | 18 | func fetchUser(ctx *gin.Context, user *User, userID string) error { 19 | tx := db.WithContext(ctx).Where("user_id = ?", userID).First(&user) 20 | return tx.Error 21 | } 22 | 23 | // Admin refers to the user with roleID >= 100 24 | 25 | func fetchAdminDetailsById(ctx *gin.Context, user *User, ID string) error { 26 | tx := db.WithContext(ctx).Where("id = ?", ID).First(&user) 27 | return tx.Error 28 | } 29 | func fetchAllAdminDetails(ctx *gin.Context, users *[]User) error { 30 | tx := db.WithContext(ctx).Where("role_id >= 100").Find(&users) 31 | return tx.Error 32 | } 33 | 34 | func getPasswordAndRole(ctx *gin.Context, userID string) (string, constants.Role, bool, error) { 35 | var user User 36 | tx := db.WithContext(ctx).Where("user_id = ? AND is_active = ?", userID, true).First(&user) 37 | return user.Password, user.RoleID, user.IsActive, tx.Error 38 | } 39 | 40 | func getUserRole(ctx *gin.Context, ID uint) (constants.Role, error) { 41 | var user User 42 | tx := db.WithContext(ctx).Where("ID = ?", ID).First(&user) 43 | return user.RoleID, tx.Error 44 | } 45 | 46 | func updatePassword(ctx *gin.Context, userID string, password string) (bool, error) { 47 | tx := db.WithContext(ctx).Model(&User{}).Where("user_id = ?", userID).Update("password", password) 48 | return tx.RowsAffected > 0, tx.Error 49 | } 50 | 51 | func updatePasswordbyGod(ctx *gin.Context, userID string, password string) (bool, error) { 52 | tx := db.WithContext(ctx).Model(&User{}).Where("user_id = ?", userID).Update("password", password) 53 | return tx.RowsAffected > 0, tx.Error 54 | } 55 | 56 | func updateRoleByAdmin(ctx *gin.Context, ID uint, roleID constants.Role) error { 57 | tx := db.WithContext(ctx).Model(&User{}).Where("ID = ?", ID).Update("role_id", roleID) 58 | return tx.Error 59 | } 60 | 61 | func setLastLogin(userID string) error { 62 | tx := db.Model(&User{}).Where("user_id = ?", userID).Update("last_login", time.Now().UnixMilli()) 63 | return tx.Error 64 | } 65 | 66 | func toggleActive(ctx *gin.Context, ID uint) (bool, error) { 67 | var currStatus bool 68 | tx := db.WithContext(ctx).Model(&User{}).Where("ID = ?", ID).Select("is_active").First(&currStatus) 69 | if tx.Error != nil { 70 | return false, tx.Error 71 | } 72 | // fmt.Printf(currentStatus.First()) 73 | tx = db.WithContext(ctx).Model(&User{}).Where("ID = ?", ID).Update("is_active", !currStatus) 74 | return !currStatus, tx.Error 75 | } 76 | -------------------------------------------------------------------------------- /rc/db.resume.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gorm.io/gorm/clause" 6 | ) 7 | 8 | func addStudentResume(ctx *gin.Context, resume string, sid uint, rid uint, resumeType ResumeType, resumeTag string) error { 9 | tx := db.WithContext(ctx).Model(&StudentRecruitmentCycleResume{}).Create(&StudentRecruitmentCycleResume{ 10 | StudentRecruitmentCycleID: sid, 11 | Resume: resume, 12 | RecruitmentCycleID: rid, 13 | ResumeType: resumeType, 14 | Tag: resumeTag, // ← Store the tag 15 | }) 16 | return tx.Error 17 | } 18 | 19 | func fetchStudentResume(ctx *gin.Context, sid uint, resumes *[]StudentRecruitmentCycleResume) error { 20 | tx := db.WithContext(ctx).Model(&StudentRecruitmentCycleResume{}).Where("student_recruitment_cycle_id = ?", sid).Find(resumes) 21 | return tx.Error 22 | } 23 | func fetchAllResumes(ctx *gin.Context, rid uint, resumes *[]AllResumeResponse) error { 24 | tx := db.WithContext(ctx).Model(&StudentRecruitmentCycleResume{}). 25 | Joins("JOIN student_recruitment_cycles ON student_recruitment_cycles.id = student_recruitment_cycle_resumes.student_recruitment_cycle_id AND student_recruitment_cycle_resumes.recruitment_cycle_id = ?", rid). 26 | Select("student_recruitment_cycles.name as name, student_recruitment_cycles.email as email, student_recruitment_cycles.id as sid, student_recruitment_cycle_resumes.id as rsid, student_recruitment_cycles.roll_no as roll_no, student_recruitment_cycle_resumes.resume as resume, student_recruitment_cycle_resumes.verified as verified, student_recruitment_cycle_resumes.action_taken_by as action_taken_by, student_recruitment_cycle_resumes.resume_type as resume_type"). // Include resume_type in the response 27 | Scan(resumes) 28 | return tx.Error 29 | } 30 | 31 | func FetchResume(ctx *gin.Context, rsid uint, sid uint) (string, error) { 32 | var resume string 33 | tx := db.WithContext(ctx).Model(&StudentRecruitmentCycleResume{}). 34 | Where("id = ? AND student_recruitment_cycle_id = ? AND verified = ?", rsid, sid, true). 35 | Pluck("resume", &resume) 36 | return resume, tx.Error 37 | } 38 | 39 | func FetchFirstResume(ctx *gin.Context, sid uint) (uint, string, error) { 40 | var resume StudentRecruitmentCycleResume 41 | tx := db.WithContext(ctx).Model(&StudentRecruitmentCycleResume{}). 42 | Where("student_recruitment_cycle_id = ? AND verified = ?", sid, true).First(&resume) 43 | return resume.ID, resume.Resume, tx.Error 44 | } 45 | 46 | func updateResumeVerify(ctx *gin.Context, rsid uint, verified bool, user string) (bool, uint, error) { 47 | var resume StudentRecruitmentCycleResume 48 | tx := db.WithContext(ctx).Model(&resume).Clauses(clause.Returning{}). 49 | Where("id = ?", rsid).Updates(map[string]interface{}{"verified": verified, "action_taken_by": user}) 50 | return tx.RowsAffected == 1, resume.StudentRecruitmentCycleID, tx.Error 51 | } 52 | -------------------------------------------------------------------------------- /application/admin.pio_ppo.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "database/sql" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | "github.com/spo-iitk/ras-backend/rc" 10 | "github.com/spo-iitk/ras-backend/util" 11 | ) 12 | 13 | func getEmptyProformaByCID(ctx *gin.Context, cid uint, jp *Proforma) error { 14 | var companyRC rc.CompanyRecruitmentCycle 15 | err := rc.FetchCompany(ctx, cid, &companyRC) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | jp.CompanyRecruitmentCycleID = companyRC.ID 21 | jp.RecruitmentCycleID = companyRC.RecruitmentCycleID 22 | jp.CompanyName = companyRC.CompanyName 23 | jp.IsApproved = sql.NullBool{Bool: true, Valid: true} 24 | jp.ActionTakenBy = middleware.GetUserID(ctx) 25 | jp.Role = string(PIOPPOACCEPTED) 26 | jp.Profile = string(PIOPPOACCEPTED) 27 | 28 | return firstOrCreatePPOProforma(ctx, jp) 29 | } 30 | 31 | type pioppoRequest struct { 32 | Cid uint `json:"cid" binding:"required"` 33 | Emails []string `json:"emails" binding:"required"` 34 | } 35 | 36 | func postPPOPIOHandler(ctx *gin.Context) { 37 | var req pioppoRequest 38 | 39 | err := ctx.ShouldBindJSON(&req) 40 | if err != nil { 41 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 42 | return 43 | } 44 | 45 | var jp Proforma 46 | err = getEmptyProformaByCID(ctx, req.Cid, &jp) 47 | if err != nil { 48 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 49 | return 50 | } 51 | 52 | rid, err := util.ParseUint(ctx.Param("rid")) 53 | if err != nil { 54 | ctx.AbortWithStatusJSON(http.StatusBadRequest, err.Error()) 55 | return 56 | } 57 | 58 | var studentIDs []uint 59 | studentIDs, req.Emails, err = rc.FetchStudentRCIDs(ctx, rid, req.Emails) 60 | if err != nil { 61 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 62 | return 63 | } 64 | 65 | err = rc.UpdateStudentType(ctx, req.Cid, req.Emails, string(PIOPPOACCEPTED)) 66 | if err != nil { 67 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 68 | return 69 | } 70 | 71 | var event = ProformaEvent{ 72 | ProformaID: jp.ID, 73 | Name: string(PIOPPOACCEPTED), 74 | } 75 | err = createEvent(ctx, &event) 76 | if err != nil { 77 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 78 | return 79 | } 80 | 81 | var ses []EventStudent 82 | for _, studentID := range studentIDs { 83 | ses = append(ses, EventStudent{ 84 | ProformaEventID: event.ID, 85 | StudentRecruitmentCycleID: studentID, 86 | CompanyRecruitmentCycleID: jp.CompanyRecruitmentCycleID, 87 | Present: true, 88 | }) 89 | } 90 | 91 | err = createEventStudents(ctx, &ses) 92 | if err != nil { 93 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 94 | return 95 | } 96 | 97 | ctx.JSON(http.StatusOK, gin.H{"status": "updated student pioppo"}) 98 | } 99 | -------------------------------------------------------------------------------- /rc/admin.question.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/sirupsen/logrus" 9 | "github.com/spo-iitk/ras-backend/middleware" 10 | ) 11 | 12 | func getStudentQuestionsHandler(ctx *gin.Context) { 13 | rid := ctx.Param("rid") 14 | var questions []RecruitmentCycleQuestion 15 | 16 | err := fetchStudentQuestions(ctx, rid, &questions) 17 | if err != nil { 18 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 19 | return 20 | } 21 | 22 | ctx.JSON(http.StatusOK, questions) 23 | } 24 | 25 | func postStudentQuestionHandler(ctx *gin.Context) { 26 | var question RecruitmentCycleQuestion 27 | 28 | err := ctx.ShouldBindJSON(&question) 29 | if err != nil { 30 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 31 | return 32 | } 33 | 34 | rid, err := strconv.ParseUint(ctx.Param("rid"), 10, 32) 35 | if err != nil { 36 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 37 | return 38 | } 39 | 40 | question.RecruitmentCycleID = uint(rid) 41 | err = createStudentQuestion(ctx, &question) 42 | if err != nil { 43 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 44 | return 45 | } 46 | 47 | user := middleware.GetUserID(ctx) 48 | 49 | logrus.Infof("%v created a student question with id %v", user, question.ID) 50 | 51 | ctx.JSON(http.StatusOK, question) 52 | } 53 | 54 | func putStudentQuestionHandler(ctx *gin.Context) { 55 | var question RecruitmentCycleQuestion 56 | 57 | err := ctx.ShouldBindJSON(&question) 58 | if err != nil { 59 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 60 | return 61 | } 62 | 63 | if question.ID == 0 { 64 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Enter question ID"}) 65 | return 66 | } 67 | 68 | ok, err := updateStudentQuestion(ctx, &question) 69 | if err != nil { 70 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 71 | return 72 | } 73 | 74 | if !ok { 75 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "No such question exists"}) 76 | return 77 | } 78 | 79 | user := middleware.GetUserID(ctx) 80 | 81 | logrus.Infof("%v updated a student question with id %d", user, question.ID) 82 | 83 | ctx.JSON(http.StatusOK, gin.H{"status": "updated student question"}) 84 | } 85 | 86 | func deleteStudentQuestionHandler(ctx *gin.Context) { 87 | qid := ctx.Param("qid") 88 | 89 | err := deleteStudentQuestion(ctx, qid) 90 | if err != nil { 91 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 92 | return 93 | } 94 | 95 | user := middleware.GetUserID(ctx) 96 | 97 | logrus.Infof("%v deleted a student question with id %v", user, qid) 98 | 99 | ctx.JSON(http.StatusOK, gin.H{"status": "deleted student question"}) 100 | } 101 | -------------------------------------------------------------------------------- /company/admin.company.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/sirupsen/logrus" 9 | m "github.com/spo-iitk/ras-backend/middleware" 10 | ) 11 | 12 | func addNewHandler(ctx *gin.Context) { 13 | var newCompanyRequest Company 14 | 15 | if err := ctx.ShouldBindJSON(&newCompanyRequest); err != nil { 16 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 17 | return 18 | } 19 | 20 | err := createCompany(ctx, &newCompanyRequest) 21 | if err != nil { 22 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 23 | return 24 | } 25 | 26 | logrus.Infof("A new company %s is added with id %d by %s", newCompanyRequest.Name, newCompanyRequest.ID, m.GetUserID(ctx)) 27 | 28 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully added"}) 29 | 30 | } 31 | 32 | func addNewBulkHandler(ctx *gin.Context) { 33 | var request []Company 34 | 35 | if err := ctx.ShouldBindJSON(&request); err != nil { 36 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 37 | return 38 | } 39 | 40 | err := createCompanies(ctx, &request) 41 | if err != nil { 42 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 43 | return 44 | } 45 | 46 | logrus.Infof("%d companies is added with by %s", len(request), m.GetUserID(ctx)) 47 | 48 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully added"}) 49 | 50 | } 51 | 52 | func updateCompanyHandler(ctx *gin.Context) { 53 | var updateCompanyRequest Company 54 | 55 | if err := ctx.ShouldBindJSON(&updateCompanyRequest); err != nil { 56 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 57 | return 58 | } 59 | 60 | if updateCompanyRequest.ID == 0 { 61 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Enter Company ID"}) 62 | return 63 | } 64 | updated, err := updateCompany(ctx, &updateCompanyRequest) 65 | if err != nil { 66 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 67 | return 68 | } 69 | 70 | if !updated { 71 | ctx.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "Company not found"}) 72 | return 73 | } 74 | 75 | logrus.Infof("A company with id %d is updated by %s", updateCompanyRequest.ID, m.GetUserID(ctx)) 76 | 77 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully updated"}) 78 | } 79 | 80 | func deleteCompanyHandler(ctx *gin.Context) { 81 | 82 | cid, err := strconv.ParseUint(ctx.Param("cid"), 10, 32) 83 | if err != nil { 84 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 85 | return 86 | } 87 | 88 | err = deleteCompany(ctx, uint(cid)) 89 | if err != nil { 90 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 91 | return 92 | } 93 | 94 | logrus.Infof("A company with id %d is deleted by %s", cid, m.GetUserID(ctx)) 95 | 96 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully deleted"}) 97 | 98 | } 99 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | # push: 16 | # branches: [ "main" ] 17 | # pull_request: 18 | # # The branches below must be a subset of the branches above 19 | # branches: [ "main" ] 20 | # monthly runs are good enough 21 | schedule: 22 | - cron: '0 0 1 * *' 23 | 24 | jobs: 25 | analyze: 26 | name: Analyze 27 | runs-on: ubuntu-latest 28 | permissions: 29 | actions: read 30 | contents: read 31 | security-events: write 32 | 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | language: [ 'go' ] 37 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 38 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v3 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v2 47 | with: 48 | languages: go 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | 53 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 54 | # queries: security-extended,security-and-quality 55 | 56 | 57 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 58 | # If this step fails, then you should remove it and run the build manually (see below) 59 | - name: Autobuild 60 | uses: github/codeql-action/autobuild@v2 61 | 62 | # ℹ️ Command-line programs to run using the OS shell. 63 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 64 | 65 | # If the Autobuild fails above, remove it and uncomment the following three lines. 66 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 67 | 68 | # - run: | 69 | # echo "Run, Build Application using script" 70 | # ./location_of_script_within_repo/buildscript.sh 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v2 74 | -------------------------------------------------------------------------------- /middleware/jwt.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/golang-jwt/jwt" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | var ( 12 | jwtExpirationLong int 13 | jwtExpirationShort int 14 | signingKey []byte 15 | ) 16 | 17 | type CustomClaims struct { 18 | UserID string `json:"user_id"` 19 | RoleID uint `json:"role_id"` 20 | jwt.StandardClaims 21 | } 22 | type CustomPVFClaims struct { 23 | Email string `json:"email"` 24 | Pid uint `json:"pid"` 25 | Rid uint `json:"rid"` 26 | jwt.StandardClaims 27 | } 28 | 29 | func init() { 30 | jwtExpirationLong = viper.GetInt("JWT.EXPIRATION.LONG") 31 | jwtExpirationShort = viper.GetInt("JWT.EXPIRATION.SHORT") 32 | signingKey = []byte(viper.GetString("JWT.PRIVATE_KEY")) 33 | } 34 | 35 | func GenerateToken(userID string, roleID uint, long bool) (string, error) { 36 | var jwtExpiration int 37 | if long { 38 | jwtExpiration = jwtExpirationLong 39 | } else { 40 | jwtExpiration = jwtExpirationShort 41 | } 42 | 43 | claims := CustomClaims{ 44 | userID, 45 | roleID, 46 | jwt.StandardClaims{ 47 | ExpiresAt: time.Now().Add(time.Duration(jwtExpiration) * time.Minute).Unix(), 48 | IssuedAt: jwt.TimeFunc().Unix(), 49 | Issuer: "ras", 50 | }, 51 | } 52 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 53 | tokenString, err := token.SignedString(signingKey) 54 | return tokenString, err 55 | } 56 | 57 | func validateToken(encodedToken string) (string, uint, error) { 58 | 59 | claims := &CustomClaims{} 60 | _, err := jwt.ParseWithClaims(encodedToken, claims, func(token *jwt.Token) (interface{}, error) { 61 | if _, isvalid := token.Method.(*jwt.SigningMethodHMAC); !isvalid { 62 | return nil, fmt.Errorf("invalid token %s", token.Header["alg"]) 63 | } 64 | return []byte(signingKey), nil 65 | }) 66 | 67 | if err != nil { 68 | return "", 20, err 69 | } 70 | 71 | return claims.UserID, claims.RoleID, nil 72 | } 73 | 74 | func GeneratePVFToken(email string, pid uint, rid uint) (string, error) { 75 | var jwtExpiration = viper.GetInt("PVF.EXPIRATION") 76 | 77 | claims := CustomPVFClaims{ 78 | email, 79 | pid, 80 | rid, 81 | jwt.StandardClaims{ 82 | ExpiresAt: time.Now().Add(time.Duration(jwtExpiration) * time.Minute).Unix(), 83 | IssuedAt: jwt.TimeFunc().Unix(), 84 | Issuer: "ras", 85 | }, 86 | } 87 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 88 | tokenString, err := token.SignedString(signingKey) 89 | return tokenString, err 90 | } 91 | 92 | func validatePVFToken(encodedToken string) (string, uint, uint, error) { 93 | 94 | claims := &CustomPVFClaims{} 95 | _, err := jwt.ParseWithClaims(encodedToken, claims, func(token *jwt.Token) (interface{}, error) { 96 | if _, isvalid := token.Method.(*jwt.SigningMethodHMAC); !isvalid { 97 | return nil, fmt.Errorf("invalid token %s", token.Header["alg"]) 98 | } 99 | return []byte(signingKey), nil 100 | }) 101 | 102 | if err != nil { 103 | return "", 20, 20, err 104 | } 105 | return claims.Email, claims.Pid, claims.Rid, nil 106 | } 107 | -------------------------------------------------------------------------------- /application/db.pvf.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func createPVF(ctx *gin.Context, pvf *PVF) error { 8 | tx := db.WithContext(ctx).Create(pvf) 9 | return tx.Error 10 | } 11 | 12 | func fetchPVF(ctx *gin.Context, pid uint, jp *PVF) error { 13 | tx := db.WithContext(ctx).Where("id = ?", pid).First(jp) 14 | return tx.Error 15 | } 16 | 17 | func updatePVF(ctx *gin.Context, jp *PVF) error { 18 | tx := db.WithContext(ctx).Where("id = ?", jp.ID).Updates(jp) 19 | return tx.Error 20 | } 21 | 22 | func fetchAllPvfForStudent(ctx *gin.Context, sid uint, rid uint, jps *[]PVF) error { 23 | tx := db.WithContext(ctx). 24 | Where("student_recruitment_cycle_id = ? AND recruitment_cycle_id = ?", sid, rid). 25 | Order("id DESC"). 26 | Find(jps) 27 | return tx.Error 28 | } 29 | func fetchAllUnverifiedPvfForStudent(ctx *gin.Context, sid uint, rid uint, jps *[]PVF) error { 30 | tx := db.WithContext(ctx). 31 | Where("student_recruitment_cycle_id = ? AND recruitment_cycle_id = ? AND is_verified is null", sid, rid). 32 | Order("id DESC"). 33 | Find(jps) 34 | return tx.Error 35 | } 36 | func fetchPvfForStudent(ctx *gin.Context, sid uint, rid uint, pid uint, jps *PVF) error { 37 | tx := db.WithContext(ctx). 38 | Where("student_recruitment_cycle_id = ? AND recruitment_cycle_id = ? AND id = ?", sid, rid, pid). 39 | Select( 40 | "id", 41 | "company_university_name", 42 | "role", 43 | "duration", 44 | "mentor_name", 45 | "mentor_designation", 46 | "mentor_email", 47 | "is_verified", 48 | "filename_mentor", 49 | "filename_student", 50 | "remarks", 51 | ). 52 | Find(jps) 53 | return tx.Error 54 | } 55 | func fetchPvfForAdmin(ctx *gin.Context, rid uint, pid uint, jps *PVF) error { 56 | tx := db.WithContext(ctx). 57 | Where("recruitment_cycle_id = ? AND id = ?", rid, pid). 58 | Order("id DESC"). 59 | Find(jps) 60 | return tx.Error 61 | } 62 | 63 | func fetchPvfForVerification(ctx *gin.Context, id uint, rid uint, jps *PVF) error { 64 | tx := db.WithContext(ctx). 65 | Where("id = ? AND recruitment_cycle_id = ?", id, rid). 66 | Select( 67 | "id", 68 | "company_university_name", 69 | "role", 70 | "duration", 71 | "mentor_name", 72 | "mentor_designation", 73 | "mentor_email", 74 | "is_verified", 75 | "is_approved", 76 | "filename_student", 77 | "filename_mentor", 78 | "remarks", 79 | ). 80 | Find(jps) 81 | return tx.Error 82 | } 83 | 84 | func fetchAllPvfForAdmin(ctx *gin.Context, rid uint, jps *[]PVF) error { 85 | tx := db.WithContext(ctx). 86 | Where("recruitment_cycle_id = ?", rid). 87 | Order("id DESC"). 88 | Find(jps) 89 | return tx.Error 90 | } 91 | 92 | func deletePVF(ctx *gin.Context, pid uint) error { 93 | tx := db.WithContext(ctx).Where("id = ?", pid).Delete(&PVF{}) 94 | return tx.Error 95 | } 96 | 97 | // func fetchAllStudentPvfForAdmin(ctx *gin.Context) 98 | 99 | func updatePVFForStudent(ctx *gin.Context, sid uint, jp *PVF) (bool, error) { 100 | tx := db.WithContext(ctx).Where("id = ? AND student_recruitment_cycle_id = ?", jp.ID, sid).Updates(jp) 101 | return tx.RowsAffected == 1, tx.Error 102 | } 103 | -------------------------------------------------------------------------------- /application/verify.pvf.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/spo-iitk/ras-backend/mail" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | "github.com/spo-iitk/ras-backend/util" 10 | ) 11 | 12 | func getPvfForVerificationHandler(ctx *gin.Context) { 13 | // ctx.JSON(http.StatusOK, gin.H{"pid": middleware.GetPVFID(ctx)}) 14 | pid := middleware.GetPVFID(ctx) 15 | 16 | // pid, err := util.ParseUint(ctx.Param("pid")) 17 | // if err != nil { 18 | // ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 19 | // return 20 | // } 21 | // rid, err := util.ParseUint(ctx.Param("rid")) 22 | // if err != nil { 23 | // ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 24 | // return 25 | // } 26 | rid := middleware.GetRcID(ctx) 27 | var jps PVF 28 | err := fetchPvfForVerification(ctx, pid, rid, &jps) 29 | if err != nil { 30 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 31 | return 32 | } 33 | 34 | ctx.JSON(http.StatusOK, jps) 35 | } 36 | 37 | func putPVFHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 38 | return func(ctx *gin.Context) { 39 | var pvf PVF 40 | 41 | err := ctx.ShouldBindJSON(&pvf) 42 | if err != nil { 43 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 44 | return 45 | } 46 | pid := middleware.GetPVFID(ctx) 47 | 48 | pvf.ID = pid 49 | 50 | if pvf.ID == 0 { 51 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "id is required"}) 52 | return 53 | } 54 | 55 | var oldJp PVF 56 | err = fetchPVF(ctx, pvf.ID, &oldJp) 57 | if err != nil { 58 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 59 | return 60 | } 61 | 62 | // jp.ActionTakenBy = middleware.GetUserID(ctx) 63 | 64 | // publishNotice := oldJp.Deadline == 0 && jp.Deadline != 0 65 | 66 | err = updatePVF(ctx, &pvf) 67 | if err != nil { 68 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 69 | return 70 | } 71 | var action string 72 | if pvf.IsVerified.Bool { 73 | action = "APPROVED" 74 | } else { 75 | action = "DENIED" 76 | } 77 | messageMentor := "Dear " + oldJp.MentorName + ",\n\n" + 78 | "The action " + action + " on the Project Verification Form of the " + oldJp.Name + " has been taken.\n\n" + 79 | "If you have not done this action please reach out to spo@iitk.ac.in\n\n" + 80 | "Regards,\n" + 81 | "Students' Placement Team, IIT kanpur" 82 | 83 | mail_channel <- mail.GenerateMail(oldJp.MentorEmail, 84 | "Project Verification Update for "+oldJp.Name+"'s Internship/Project", 85 | messageMentor, 86 | ) 87 | messageStudent := "Dear " + oldJp.Name + ",\n\n" + 88 | 89 | "Action has been taken on your Project Verification Form. Kindly check the status on the RAS Portal." + 90 | "\n\n" + 91 | "Regards,\n" + 92 | "Students' Placement Team, IIT kanpur" 93 | 94 | mail_channel <- mail.GenerateMail(oldJp.IITKEmail, 95 | "Project Verification Update for "+oldJp.Name+"'s Internship/Project", 96 | messageStudent, 97 | ) 98 | ctx.JSON(http.StatusOK, gin.H{"status": "Updated PVF with id " + util.ParseString(pvf.ID)}) 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /rc/router.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/spo-iitk/ras-backend/mail" 6 | ) 7 | 8 | func AdminRouter(mail_channel chan mail.Mail, r *gin.Engine) { 9 | r.GET("/api/admin/rc", getAllRCHandler) 10 | r.POST("/api/admin/rc", postRCHandler) 11 | r.PUT("/api/admin/rc", editRCHandler) 12 | 13 | admin := r.Group("/api/admin/rc/:rid") 14 | admin.Use(checkAdminAccessToRC()) 15 | { 16 | admin.GET("", getRCHandler) 17 | admin.GET("/count", getRCCountHandler) 18 | 19 | admin.GET("/notice", getAllNoticesHandler) 20 | admin.POST("/notice", postNoticeHandler(mail_channel)) 21 | admin.PUT("/notice", putNoticeHandler) 22 | admin.POST("/notice/:nid/reminder", postReminderHandler(mail_channel)) 23 | admin.DELETE("/notice/:nid", deleteNoticeHandler) 24 | 25 | admin.GET("/company", getAllCompaniesHandler) 26 | admin.POST("/company", postNewCompanyHandler) 27 | admin.PUT("/company", putCompanyHandler) 28 | admin.GET("/company/:cid", getCompanyHandler) 29 | admin.DELETE("/company/:cid", deleteCompanyHandler) 30 | admin.GET("/company/:cid/history", getCompanyHistoryHandler) 31 | admin.GET("/company/:cid/ids", getCompanyAllRCID) 32 | 33 | admin.GET("/student", getAllStudentsHandler) 34 | 35 | admin.GET("/student/:sid", getStudentHandler) 36 | admin.POST("/student/:sid/clarification", postClarificationHandler(mail_channel)) 37 | admin.DELETE("/student/:sid", deleteStudentHandler) 38 | 39 | admin.POST("/student", postStudentsHandler(mail_channel)) 40 | admin.PUT("/student", putStudentHandler) 41 | 42 | admin.PUT("/student/freeze", bulkFreezeStudentsHandler(mail_channel)) 43 | 44 | admin.PUT("/student/sync", syncStudentsHandler) 45 | // route not in use 46 | // admin.PUT("/student/deregister", deregisterAllStudentsHandler) 47 | 48 | admin.GET("/student/questions", getStudentQuestionsHandler) 49 | admin.POST("/student/question", postStudentQuestionHandler) 50 | admin.PUT("/student/question", putStudentQuestionHandler) 51 | admin.DELETE("/student/question/:qid", deleteStudentQuestionHandler) 52 | 53 | admin.GET("/student/:sid/question/answers", getStudentAnswersHandler) 54 | admin.GET("/student/:sid/resume", getResumesHandler) 55 | 56 | admin.GET("/resume", getAllResumesHandler) 57 | admin.PUT("/resume/:rsid/verify", putResumeVerifyHandler(mail_channel)) 58 | } 59 | } 60 | 61 | func StudentRouter(r *gin.Engine) { 62 | r.GET("/api/student/rc", getStudentRCHandler) 63 | r.GET("/api/student/rc/:rid", studentWhoamiHandler) 64 | student := r.Group("/api/student/rc/:rid") 65 | student.Use(ensureActiveStudent()) 66 | { 67 | student.GET("/notice", getAllNoticesForStudentHandler) 68 | 69 | student.GET("/enrollment", getStudentEnrollmentHandler) 70 | student.POST("/enrollment/:qid/answer", postEnrollmentAnswerHandler) 71 | 72 | student.POST("/resume", postStudentResumeHandler) 73 | student.GET("/resume", getStudentResumeHandler) 74 | } 75 | } 76 | 77 | func CompanyRouter(r *gin.Engine) { 78 | r.GET("/api/company/whoami", companyWhoamiHandler) 79 | company := r.Group("/api/company/rc") 80 | { 81 | company.GET("", getCompanyRCHandler) // get registered rc 82 | company.GET("/all", getAllRCHandlerForCompany) // get all rc 83 | company.POST("/:rid/enrollment", enrollCompanyHandler) // enroll a company to a rc 84 | company.GET("/:rid/hr", getCompanyRCHRHandler) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /middleware/authenticator.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/spo-iitk/ras-backend/constants" 9 | ) 10 | 11 | func Authenticator() gin.HandlerFunc { 12 | return func(ctx *gin.Context) { 13 | authorizationHeader := ctx.GetHeader("authorization") 14 | if len(authorizationHeader) == 0 { 15 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, 16 | gin.H{"error": "authorization header is not provided"}) 17 | return 18 | } 19 | 20 | fields := strings.Fields(authorizationHeader) 21 | if len(fields) < 2 { 22 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, 23 | gin.H{"error": "invalid authorization header format"}) 24 | return 25 | } 26 | 27 | authorizationType := strings.ToLower(fields[0]) 28 | if authorizationType != ("bearer") { 29 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, 30 | gin.H{"error": "bearer not found"}) 31 | return 32 | } 33 | 34 | userID, roleID, err := validateToken(fields[1]) 35 | if err != nil { 36 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, 37 | gin.H{"error": "invalid token"}) 38 | return 39 | } 40 | 41 | // cookie, err := ctx.Request.Cookie("token") 42 | // if err != nil { 43 | // ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) 44 | // return 45 | // } 46 | 47 | // userID, roleID, err := validateToken(cookie.Value) 48 | // if err != nil { 49 | // ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid Cookies"}) 50 | // return 51 | // } 52 | 53 | ctx.Set("userID", userID) 54 | ctx.Set("roleID", int(roleID)) 55 | 56 | ctx.Next() 57 | } 58 | } 59 | 60 | func GetUserID(ctx *gin.Context) string { 61 | return ctx.GetString("userID") 62 | } 63 | 64 | func GetRoleID(ctx *gin.Context) constants.Role { 65 | 66 | return constants.Role(ctx.GetInt("roleID")) 67 | } 68 | 69 | func PVFAuthenticator() gin.HandlerFunc { 70 | return func(ctx *gin.Context) { 71 | authorizationHeader := ctx.GetHeader("authorization") 72 | if len(authorizationHeader) == 0 { 73 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, 74 | gin.H{"error": "authorization header is not provided"}) 75 | return 76 | } 77 | 78 | fields := strings.Fields(authorizationHeader) 79 | if len(fields) < 2 { 80 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, 81 | gin.H{"error": "invalid authorization header format"}) 82 | return 83 | } 84 | 85 | authorizationType := strings.ToLower(fields[0]) 86 | if authorizationType != ("bearer") { 87 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, 88 | gin.H{"error": "bearer not found"}) 89 | return 90 | } 91 | 92 | email, pid, rid, err := validatePVFToken(fields[1]) 93 | // ctx.JSON(http.StatusAccepted, gin.H{"email": email, "pid": pid, "rid": rid}) // to be removed 94 | if err != nil { 95 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, 96 | gin.H{"error": "invalid token"}) 97 | return 98 | } 99 | ctx.Set("email", email) 100 | ctx.Set("pid", pid) 101 | ctx.Set("rid", rid) 102 | 103 | ctx.Next() 104 | } 105 | } 106 | 107 | func GetEmail(ctx *gin.Context) string { 108 | return ctx.GetString("email") 109 | } 110 | 111 | func GetPVFID(ctx *gin.Context) uint { 112 | return ctx.GetUint("pid") 113 | } 114 | func GetRcID(ctx *gin.Context) uint { 115 | return ctx.GetUint("rid") 116 | } 117 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/spo-iitk/ras-backend 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.9.1 7 | github.com/golang-jwt/jwt v3.2.2+incompatible 8 | github.com/sirupsen/logrus v1.9.0 9 | github.com/spf13/viper v1.13.0 10 | golang.org/x/crypto v0.9.0 11 | golang.org/x/sync v0.1.0 12 | gorm.io/driver/postgres v1.4.4 13 | gorm.io/gorm v1.24.0 14 | ) 15 | 16 | require ( 17 | cloud.google.com/go/compute v1.10.0 // indirect 18 | github.com/bytedance/sonic v1.9.1 // indirect 19 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 20 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 21 | github.com/goccy/go-json v0.10.2 // indirect 22 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 23 | github.com/google/uuid v1.3.0 // indirect 24 | github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect 25 | github.com/googleapis/gax-go/v2 v2.6.0 // indirect 26 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 27 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 28 | go.opencensus.io v0.23.0 // indirect 29 | golang.org/x/arch v0.3.0 // indirect 30 | golang.org/x/net v0.10.0 // indirect 31 | golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect 32 | google.golang.org/appengine v1.6.7 // indirect 33 | google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a // indirect 34 | google.golang.org/grpc v1.50.1 // indirect 35 | ) 36 | 37 | require ( 38 | github.com/fsnotify/fsnotify v1.6.0 // indirect 39 | github.com/gin-contrib/sse v0.1.0 // indirect 40 | github.com/go-playground/locales v0.14.1 // indirect 41 | github.com/go-playground/universal-translator v0.18.1 // indirect 42 | github.com/go-playground/validator/v10 v10.14.0 // indirect 43 | github.com/golang/protobuf v1.5.2 // indirect 44 | github.com/hashicorp/hcl v1.0.0 // indirect 45 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 46 | github.com/jackc/pgconn v1.13.0 // indirect 47 | github.com/jackc/pgio v1.0.0 // indirect 48 | github.com/jackc/pgpassfile v1.0.0 // indirect 49 | github.com/jackc/pgproto3/v2 v2.3.1 // indirect 50 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect 51 | github.com/jackc/pgtype v1.12.0 // indirect 52 | github.com/jackc/pgx/v4 v4.17.2 // indirect 53 | github.com/jinzhu/inflection v1.0.0 // indirect 54 | github.com/jinzhu/now v1.1.5 // indirect 55 | github.com/json-iterator/go v1.1.12 // indirect 56 | github.com/leodido/go-urn v1.2.4 // indirect 57 | github.com/magiconair/properties v1.8.6 // indirect 58 | github.com/mattn/go-isatty v0.0.19 // indirect 59 | github.com/mitchellh/mapstructure v1.5.0 // indirect 60 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 61 | github.com/modern-go/reflect2 v1.0.2 // indirect 62 | github.com/pelletier/go-toml v1.9.5 // indirect 63 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 64 | github.com/spf13/afero v1.9.2 // indirect 65 | github.com/spf13/cast v1.5.0 // indirect 66 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 67 | github.com/spf13/pflag v1.0.5 // indirect 68 | github.com/subosito/gotenv v1.4.1 // indirect 69 | github.com/ugorji/go/codec v1.2.11 // indirect 70 | golang.org/x/sys v0.8.0 // indirect 71 | golang.org/x/text v0.9.0 // indirect 72 | google.golang.org/api v0.99.0 73 | google.golang.org/protobuf v1.30.0 // indirect 74 | gopkg.in/ini.v1 v1.67.0 // indirect 75 | gopkg.in/yaml.v2 v2.4.0 // indirect 76 | gopkg.in/yaml.v3 v3.0.1 // indirect 77 | ) 78 | -------------------------------------------------------------------------------- /rc/db.company.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import "github.com/gin-gonic/gin" 4 | import "fmt" 5 | 6 | func fetchAllCompanies(ctx *gin.Context, rid string, companies *[]CompanyRecruitmentCycle) error { 7 | tx := db.WithContext(ctx).Where("recruitment_cycle_id = ?", rid).Find(companies) 8 | return tx.Error 9 | } 10 | 11 | func fetchCompanyByRCIDAndCID(ctx *gin.Context, rid uint, cid uint, company *CompanyRecruitmentCycle) error { 12 | tx := db.WithContext(ctx).Where("recruitment_cycle_id = ? AND company_id = ?", rid, cid).First(company) 13 | return tx.Error 14 | } 15 | 16 | func FetchCompany(ctx *gin.Context, cid uint, company *CompanyRecruitmentCycle) error { 17 | tx := db.WithContext(ctx).Where("id = ?", cid).First(company) 18 | return tx.Error 19 | } 20 | 21 | func upsertCompany(ctx *gin.Context, company *CompanyRecruitmentCycle) error { 22 | tx := db.WithContext(ctx). 23 | Where("company_id = ? AND recruitment_cycle_id = ?", company.CompanyID, company.RecruitmentCycleID). 24 | Updates(company) 25 | if tx.Error != nil { 26 | return tx.Error 27 | } 28 | 29 | if tx.RowsAffected == 0 { 30 | tx = db.WithContext(ctx). 31 | Where("company_id = ? AND recruitment_cycle_id = ?", company.CompanyID, company.RecruitmentCycleID). 32 | FirstOrCreate(company) 33 | } 34 | return tx.Error 35 | } 36 | 37 | func editCompany(ctx *gin.Context, company *CompanyRecruitmentCycle) error { 38 | tx := db.WithContext(ctx).Where("id = ?", company.ID).Updates(company) 39 | return tx.Error 40 | } 41 | 42 | func FetchCompanyRCID(ctx *gin.Context, rid uint, companyid uint) (uint, error) { 43 | var company CompanyRecruitmentCycle 44 | tx := db.WithContext(ctx).Where("recruitment_cycle_id = ? AND company_id = ?", rid, companyid).First(&company) 45 | return company.ID, tx.Error 46 | } 47 | 48 | func getRegisteredCompanyCount(ctx *gin.Context, rid uint) (int, error) { 49 | var count int64 50 | tx := db.WithContext(ctx).Model(&CompanyRecruitmentCycle{}).Where("recruitment_cycle_id = ?", rid).Count(&count) 51 | return int(count), tx.Error 52 | } 53 | 54 | func deleteRCCompany(ctx *gin.Context, cid uint) error { 55 | tx := db.WithContext(ctx).Where("id = ?", cid).Delete(&CompanyRecruitmentCycle{}) 56 | return tx.Error 57 | } 58 | 59 | func fetchCompanyAllRecruitmentCycles(ctx *gin.Context, companyID uint, stats *[]CompanyAllRecruitmentCycle) error { 60 | tx := db.WithContext(ctx). 61 | Table("company_recruitment_cycles"). 62 | Select("company_recruitment_cycles.id, company_recruitment_cycles.recruitment_cycle_id, recruitment_cycles.type, recruitment_cycles.phase"). 63 | Joins("JOIN recruitment_cycles ON company_recruitment_cycles.recruitment_cycle_id = recruitment_cycles.id"). 64 | Where("company_recruitment_cycles.company_id = ?", companyID). 65 | Find(stats) 66 | 67 | return tx.Error 68 | } 69 | 70 | func FetchCompanyHistory(ctx *gin.Context, companyID uint, companyHistory *[]CompanyHistory) error { 71 | tx := db.WithContext(ctx). 72 | Table("company_recruitment_cycles"). 73 | Select("company_recruitment_cycles.id, company_recruitment_cycles.recruitment_cycle_id, recruitment_cycles.type, recruitment_cycles.phase, company_recruitment_cycles.comments"). 74 | Joins("JOIN recruitment_cycles ON company_recruitment_cycles.recruitment_cycle_id = recruitment_cycles.id"). 75 | Where("company_recruitment_cycles.company_id = ?", companyID). 76 | Find(companyHistory) 77 | 78 | if tx.Error != nil { 79 | fmt.Println("Error fetching company history:", tx.Error) 80 | return tx.Error 81 | } 82 | 83 | return nil 84 | } -------------------------------------------------------------------------------- /rc/admin.resume.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "database/sql" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/sirupsen/logrus" 9 | "github.com/spo-iitk/ras-backend/mail" 10 | "github.com/spo-iitk/ras-backend/middleware" 11 | "github.com/spo-iitk/ras-backend/util" 12 | ) 13 | 14 | type AllResumeResponse struct { 15 | Name string `json:"name"` 16 | Email string `json:"email"` 17 | Sid uint `json:"sid"` 18 | Rsid uint `json:"rsid"` 19 | Resume string `json:"resume"` 20 | Verified sql.NullBool `json:"verified"` 21 | ActionTakenBy string `json:"action_taken_by"` 22 | RollNo string `json:"roll_no"` 23 | ResumeType ResumeType `json:"resume_type"` 24 | } 25 | 26 | func getAllResumesHandler(ctx *gin.Context) { 27 | rid, err := util.ParseUint(ctx.Param("rid")) 28 | if err != nil { 29 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 30 | return 31 | } 32 | 33 | var resumes []AllResumeResponse 34 | err = fetchAllResumes(ctx, rid, &resumes) 35 | if err != nil { 36 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 37 | return 38 | } 39 | 40 | ctx.JSON(http.StatusOK, resumes) 41 | } 42 | 43 | func getResumesHandler(ctx *gin.Context) { 44 | sid, err := util.ParseUint(ctx.Param("sid")) 45 | if err != nil { 46 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 47 | } 48 | 49 | var resumes []StudentRecruitmentCycleResume 50 | err = fetchStudentResume(ctx, sid, &resumes) 51 | if err != nil { 52 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 53 | return 54 | } 55 | 56 | ctx.JSON(http.StatusOK, resumes) 57 | } 58 | 59 | type putResumeVerifyRequest struct { 60 | Verified bool `json:"verified"` 61 | } 62 | 63 | func putResumeVerifyHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 64 | return func(ctx *gin.Context) { 65 | 66 | rsid, err := util.ParseUint(ctx.Param("rsid")) 67 | if err != nil { 68 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 69 | return 70 | } 71 | 72 | var req putResumeVerifyRequest 73 | 74 | err = ctx.BindJSON(&req) 75 | if err != nil { 76 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 77 | return 78 | } 79 | 80 | user := middleware.GetUserID(ctx) 81 | 82 | ok, studentRCID, err := updateResumeVerify(ctx, rsid, req.Verified, user) 83 | if err != nil { 84 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 85 | return 86 | } 87 | 88 | if !ok { 89 | ctx.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "resume not found"}) 90 | return 91 | } 92 | 93 | var student StudentRecruitmentCycle 94 | err = FetchStudent(ctx, studentRCID, &student) 95 | if err != nil { 96 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 97 | return 98 | } 99 | 100 | logrus.Infof("%v verified resume with id %d, changed state to %v", user, rsid, req.Verified) 101 | 102 | msg := "Dear " + student.Name + "\n\n" 103 | msg += "Your resume with id " + ctx.Param("rsid") + " has been " 104 | if req.Verified { 105 | msg += "ACCEPTED" 106 | } else { 107 | msg += "REJECTED" 108 | } 109 | mail_channel <- mail.GenerateMail(student.Email, "Action taken on resume", msg) 110 | 111 | ctx.JSON(http.StatusOK, gin.H{"status": true}) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /application/student.pvf.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | "github.com/spo-iitk/ras-backend/util" 10 | ) 11 | 12 | func postPvfForStudentHandler(ctx *gin.Context) { 13 | sid := getStudentRCID(ctx) 14 | if sid == 0 { 15 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "SRCID not found"}) 16 | return 17 | } 18 | var pvf PVF 19 | err := ctx.ShouldBindJSON(&pvf) 20 | if err != nil { 21 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 22 | return 23 | } 24 | pvf.StudentRecruitmentCycleID = sid 25 | err = createPVF(ctx, &pvf) 26 | if err != nil { 27 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 28 | return 29 | } 30 | user := middleware.GetUserID(ctx) 31 | 32 | logrus.Infof("%v created \a pvf with id %d", user, pvf.ID) 33 | ctx.JSON(http.StatusOK, gin.H{"pid": pvf.ID}) 34 | 35 | } 36 | 37 | func getAllPvfForStudentHandler(ctx *gin.Context) { 38 | sid := getStudentRCID(ctx) 39 | if sid == 0 { 40 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "SRCID not found"}) 41 | return 42 | } 43 | rid, err := util.ParseUint(ctx.Param("rid")) 44 | if err != nil { 45 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 46 | return 47 | } 48 | var jps []PVF 49 | err = fetchAllPvfForStudent(ctx, sid, rid, &jps) 50 | if err != nil { 51 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 52 | return 53 | } 54 | 55 | ctx.JSON(http.StatusOK, jps) 56 | 57 | } 58 | func getPvfForStudentHandler(ctx *gin.Context) { 59 | sid := getStudentRCID(ctx) 60 | if sid == 0 { 61 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "SRCID not found"}) 62 | return 63 | } 64 | rid, err := util.ParseUint(ctx.Param("rid")) 65 | if err != nil { 66 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 67 | return 68 | } 69 | pid, err := util.ParseUint(ctx.Param("pid")) 70 | if err != nil { 71 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 72 | return 73 | } 74 | var jps PVF 75 | err = fetchPvfForStudent(ctx, sid, rid, pid, &jps) 76 | if err != nil { 77 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 78 | return 79 | } 80 | 81 | ctx.JSON(http.StatusOK, jps) 82 | } 83 | 84 | func putPVFForStudentHandler(ctx *gin.Context) { 85 | sid := getStudentRCID(ctx) 86 | if sid == 0 { 87 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "SRCID not found"}) 88 | return 89 | } 90 | var jp PVF 91 | 92 | err := ctx.ShouldBindJSON(&jp) 93 | if err != nil { 94 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 95 | return 96 | } 97 | 98 | if jp.ID == 0 { 99 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "id is required"}) 100 | return 101 | } 102 | 103 | var oldJp PVF 104 | err = fetchPVF(ctx, jp.ID, &oldJp) 105 | if err != nil { 106 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 107 | return 108 | } 109 | 110 | ok, err := updatePVFForStudent(ctx, sid, &jp) 111 | if err != nil { 112 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 113 | return 114 | } 115 | 116 | if !ok { 117 | ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "pvf not found or unauthorized"}) 118 | return 119 | } 120 | ctx.JSON(http.StatusOK, gin.H{"status": "Updated PVF with id " + util.ParseString(jp.ID)}) 121 | } 122 | -------------------------------------------------------------------------------- /rc/admin.rc.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func getAllRCHandler(ctx *gin.Context) { 10 | var rc []RecruitmentCycle 11 | err := fetchAllRCs(ctx, &rc) 12 | if err != nil { 13 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 14 | return 15 | } 16 | 17 | if(ctx.GetInt("roleID") > 101){ 18 | var activeRC []RecruitmentCycle 19 | for _, element := range rc { 20 | if element.IsActive { 21 | activeRC = append(activeRC, element) 22 | } 23 | } 24 | rc = activeRC 25 | } 26 | 27 | ctx.JSON(http.StatusOK, rc) 28 | } 29 | 30 | type RC struct { 31 | IsActive bool `json:"is_active" gorm:"default:true"` 32 | AcademicYear string `json:"academic_year" binding:"required"` 33 | Type RecruitmentCycleType `json:"type" binding:"required"` 34 | StartDate int64 `json:"start_date" binding:"required"` 35 | Phase string `json:"phase" binding:"required"` 36 | ApplicationCountCap uint `json:"application_count_cap" binding:"required"` 37 | } 38 | 39 | func postRCHandler(ctx *gin.Context) { 40 | var recruitmentCycle RC 41 | err := ctx.ShouldBindJSON(&recruitmentCycle) 42 | if err != nil { 43 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 44 | return 45 | } 46 | 47 | var rc = RecruitmentCycle{ 48 | IsActive: recruitmentCycle.IsActive, 49 | AcademicYear: recruitmentCycle.AcademicYear, 50 | Type: recruitmentCycle.Type, 51 | StartDate: recruitmentCycle.StartDate, 52 | Phase: recruitmentCycle.Phase, 53 | ApplicationCountCap: recruitmentCycle.ApplicationCountCap, 54 | } 55 | 56 | err = createRC(ctx, &rc) 57 | if err != nil { 58 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 59 | return 60 | } 61 | ctx.JSON(201, gin.H{"id": rc.ID}) 62 | } 63 | 64 | //! TODO: Add more response data 65 | type getRCResponse struct { 66 | RecruitmentCycle 67 | } 68 | 69 | func getRCHandler(ctx *gin.Context) { 70 | id := ctx.Param("rid") 71 | var rc RecruitmentCycle 72 | err := fetchRC(ctx, id, &rc) 73 | if err != nil { 74 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 75 | return 76 | } 77 | ctx.JSON(http.StatusOK, getRCResponse{rc}) 78 | } 79 | 80 | func GetMaxCountfromRC(ctx *gin.Context) (uint, error) { 81 | id := ctx.Param("rid") 82 | var rc RecruitmentCycle 83 | 84 | err := fetchRC(ctx, id, &rc) 85 | if err != nil { 86 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 87 | return 0, err 88 | } 89 | 90 | return rc.ApplicationCountCap, nil 91 | } 92 | 93 | type editRCRequest struct { 94 | ID uint `json:"id" binding:"required"` 95 | Inactive bool `json:"inactive"` 96 | ApplicationCountCap uint `json:"application_count_cap"` 97 | } 98 | 99 | func editRCHandler(ctx *gin.Context) { 100 | var req editRCRequest 101 | err := ctx.ShouldBindJSON(&req) 102 | if err != nil { 103 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 104 | return 105 | } 106 | 107 | ok, err := updateRC(ctx, req.ID, req.Inactive, req.ApplicationCountCap) 108 | if err != nil { 109 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 110 | return 111 | } 112 | 113 | if !ok { 114 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Could not find data"}) 115 | return 116 | } 117 | 118 | ctx.JSON(http.StatusOK, gin.H{"status": "Updated Succesfully"}) 119 | } 120 | -------------------------------------------------------------------------------- /mail/service.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net/smtp" 7 | "strings" 8 | "text/template" 9 | "time" 10 | 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | type Mail struct { 15 | To []string 16 | Subject string 17 | Body string 18 | } 19 | 20 | func (mail *Mail) BuildMessage() []byte { 21 | 22 | type TemplateData struct { 23 | To []string 24 | Subject string 25 | Body string 26 | } 27 | 28 | var message strings.Builder 29 | 30 | msg := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\r\n" 31 | msg += fmt.Sprintf("From: Recruitment Automation System IITK<%s>\r\n", sender) 32 | msg += fmt.Sprintf("Subject: %s\r\n", mail.Subject) 33 | 34 | // If mass mailing, BCC all the users 35 | if len(mail.To) == 1 { 36 | msg += fmt.Sprintf("To: %s\r\n\r\n", mail.To[0]) 37 | } else { 38 | msg += fmt.Sprintf("To: Undisclosed Recipients<%s>\r\n\r\n", webteam) 39 | } 40 | 41 | message.WriteString(msg) 42 | 43 | bodyWithLineBreaks := strings.ReplaceAll(mail.Body, "\n", "
") 44 | 45 | tmpl := template.Must(template.New("Template").Parse(DefaultTemplate)) 46 | err := tmpl.Execute(&message, TemplateData{ 47 | Subject: mail.Subject, 48 | Body: bodyWithLineBreaks, 49 | }) 50 | if err != nil { 51 | logrus.Errorf("Error executing email template: %v", err) 52 | return nil 53 | } 54 | 55 | return []byte(message.String()) 56 | } 57 | 58 | func batchEmails(to []string, batch int) [][]string { 59 | var batches [][]string 60 | for i := 0; i < len(to); i += batch { 61 | end := i + batch 62 | 63 | if end > len(to) { 64 | end = len(to) 65 | } 66 | 67 | batches = append(batches, to[i:end]) 68 | } 69 | 70 | return batches 71 | } 72 | 73 | func Service(mailQueue chan Mail) { 74 | addr := fmt.Sprintf("%s:%s", host, port) 75 | auth := smtp.PlainAuth("", user, pass, host) 76 | 77 | for mail := range mailQueue { 78 | message := mail.BuildMessage() 79 | to := append(mail.To, webteam) 80 | batches := batchEmails(to, batch) 81 | for _, emailBatch := range batches { 82 | 83 | tlsconfig := &tls.Config{ 84 | InsecureSkipVerify: true, 85 | ServerName: host, 86 | } 87 | 88 | conn, err := tls.Dial("tcp", addr, tlsconfig) 89 | if err != nil { 90 | logrus.Errorf("TLS dial error: %v", err) 91 | continue 92 | } 93 | 94 | client, err := smtp.NewClient(conn, host) 95 | if err != nil { 96 | logrus.Errorf("SMTP client creation error %v", err) 97 | client.Close() 98 | continue 99 | } 100 | 101 | if err = client.Auth(auth); err != nil { 102 | logrus.Errorf("SMTP auth error: %v", err) 103 | client.Close() 104 | continue 105 | } 106 | 107 | if err = client.Mail(sender); err != nil { 108 | logrus.Errorf("SMTP sender error: %v", err) 109 | client.Close() 110 | continue 111 | } 112 | 113 | for _, addr := range emailBatch { 114 | if err = client.Rcpt(addr); err != nil { 115 | logrus.Errorf("SMTP recipient error (%s): %v", addr, err) 116 | client.Close() 117 | continue 118 | } 119 | } 120 | 121 | w, err := client.Data() 122 | if err != nil { 123 | logrus.Errorf("SMTP data error: %v", err) 124 | client.Close() 125 | continue 126 | } 127 | 128 | _, err = w.Write(message) 129 | if err != nil { 130 | logrus.Errorf("SMTP write error: %v", err) 131 | } 132 | 133 | err = w.Close() 134 | if err != nil { 135 | logrus.Errorf("SMTP close error: %v", err) 136 | } 137 | 138 | if err = client.Quit(); err != nil { 139 | logrus.Errorf("SMTP quit failed: %v", err) 140 | } 141 | 142 | time.Sleep(1 * time.Second) 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /student/admin.document.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/sirupsen/logrus" 9 | "github.com/spo-iitk/ras-backend/mail" 10 | "github.com/spo-iitk/ras-backend/middleware" 11 | "github.com/spo-iitk/ras-backend/util" 12 | ) 13 | 14 | func getDocumentHandler(ctx *gin.Context) { 15 | sid, err := strconv.ParseUint(ctx.Param("sid"), 10, 32) 16 | if err != nil { 17 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 18 | return 19 | } 20 | 21 | var documents []StudentDocument 22 | err = getDocumentsByStudentID(ctx, &documents, uint(sid)) 23 | if err != nil { 24 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 25 | return 26 | } 27 | 28 | ctx.JSON(http.StatusOK, documents) 29 | } 30 | 31 | type putDocumentVerifyRequest struct { 32 | Verified bool `json:"verified"` 33 | } 34 | 35 | func putDocumentVerifyHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 36 | return func(ctx *gin.Context) { 37 | did, err := util.ParseUint(ctx.Param("docid")) 38 | if err != nil { 39 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 40 | return 41 | } 42 | 43 | var req putDocumentVerifyRequest 44 | 45 | err = ctx.BindJSON(&req) 46 | if err != nil { 47 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 48 | return 49 | } 50 | 51 | user := middleware.GetUserID(ctx) 52 | 53 | var document StudentDocument 54 | err = getDocumentByID(ctx, &document, uint(did)) 55 | if err != nil { 56 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 57 | return 58 | } 59 | 60 | ok, err := updateDocumentVerify(ctx, did, req.Verified, user) 61 | if err != nil { 62 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 63 | return 64 | } 65 | 66 | if !ok { 67 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Could not verify document"}) 68 | return 69 | } 70 | 71 | var student Student 72 | err = getStudentByID(ctx, &student, document.StudentID) 73 | if err != nil { 74 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 75 | return 76 | } 77 | 78 | logrus.Infof("%v verified document with id %d, changed state to %v", user, did, req.Verified) 79 | 80 | // Constructing the email message 81 | msg := "Dear " + student.Name + "\n\n" 82 | msg += "Your document (" + document.Type + ") with id " + strconv.Itoa(int(did)) + " has been " 83 | if req.Verified { 84 | msg += "ACCEPTED." 85 | } else { 86 | msg += "REJECTED." 87 | } 88 | msg += "\n\nBest regards,\nYour Verification Team" 89 | 90 | mail_channel <- mail.GenerateMail(student.PersonalEmail, "Action taken on document", msg) 91 | 92 | ctx.JSON(http.StatusOK, gin.H{"status": true}) 93 | } 94 | } 95 | 96 | 97 | func getAllDocumentHandler(ctx *gin.Context) { 98 | var documents []StudentDocument 99 | err := getAllDocuments(ctx, &documents) 100 | if err != nil { 101 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 102 | return 103 | } 104 | 105 | ctx.JSON(http.StatusOK, documents) 106 | } 107 | 108 | func getAllDocumentHandlerByType(ctx *gin.Context) { 109 | docType := ctx.Param("type") 110 | var documents []StudentDocument 111 | err := getDocumentsByType(ctx, &documents, docType) 112 | if err != nil { 113 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 114 | return 115 | } 116 | 117 | ctx.JSON(http.StatusOK, documents) 118 | } -------------------------------------------------------------------------------- /student/admin.update.go: -------------------------------------------------------------------------------- 1 | package student 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | "github.com/spo-iitk/ras-backend/util" 10 | ) 11 | 12 | func updateStudentByIDHandler(ctx *gin.Context) { 13 | var updateStudentRequest Student 14 | 15 | if err := ctx.ShouldBindJSON(&updateStudentRequest); err != nil { 16 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 17 | return 18 | } 19 | 20 | if updateStudentRequest.ID == 0 { 21 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Enter student ID"}) 22 | return 23 | } 24 | 25 | // if updateStudentRequest.SecondaryProgramDepartmentID > updateStudentRequest.ProgramDepartmentID && util.IsDoubleMajor(updateStudentRequest.SecondaryProgramDepartmentID) { 26 | // ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Secondary program department and primary program department seems to be interchanged"}) 27 | // return 28 | // } 29 | /// Will check to the above code later on currently commenting it out as my primary program department is 7 (BT MSE) and when selecting secondary is 33 and its also a double major so i can't figure out the error 30 | //// I will check it later on @Akshat23 31 | 32 | updated, err := updateStudentByID(ctx, &updateStudentRequest) 33 | if err != nil { 34 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 35 | return 36 | } 37 | 38 | if !updated { 39 | ctx.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "Student not found"}) 40 | return 41 | } 42 | 43 | logrus.Infof("A student with id %d is updated by %s", updateStudentRequest.ID, ctx.GetString("userID")) 44 | 45 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully updated"}) 46 | } 47 | 48 | func verifyStudentHandler(ctx *gin.Context) { 49 | var verifyStudentRequest Student 50 | 51 | if err := ctx.ShouldBindJSON(&verifyStudentRequest); err != nil { 52 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 53 | return 54 | } 55 | 56 | sid, err := util.ParseUint(ctx.Param("sid")) 57 | if err != nil { 58 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 59 | return 60 | } 61 | 62 | verifyStudentRequest.ID = sid 63 | updated, err := verifyStudent(ctx, &verifyStudentRequest) 64 | if err != nil { 65 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 66 | return 67 | } 68 | 69 | if !updated { 70 | ctx.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "Student not found"}) 71 | return 72 | } 73 | 74 | if verifyStudentRequest.IsVerified { 75 | logrus.Infof("A student with id %d is verified", verifyStudentRequest.ID) 76 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully verified"}) 77 | } else { 78 | logrus.Infof("A student with id %d is unverified", verifyStudentRequest.ID) 79 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully unverified"}) 80 | } 81 | } 82 | 83 | func makeStudentEdiatableHandler(ctx *gin.Context) { 84 | var editableStudentRequest Student 85 | 86 | if err := ctx.ShouldBindJSON(&editableStudentRequest); err != nil { 87 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 88 | return 89 | } 90 | 91 | sid, err := util.ParseUint(ctx.Param("sid")) 92 | if err != nil { 93 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 94 | return 95 | } 96 | 97 | err = updateIsEditableWithID(ctx, sid, true) 98 | if err != nil { 99 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 100 | return 101 | } 102 | 103 | user := middleware.GetUserID(ctx) 104 | 105 | logrus.Infof("A student with id %d is made editable by %v", editableStudentRequest.ID, user) 106 | ctx.JSON(http.StatusOK, gin.H{"status": "Successfully made student editable"}) 107 | } 108 | -------------------------------------------------------------------------------- /application/util.calendar.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/sirupsen/logrus" 8 | "github.com/spf13/viper" 9 | "google.golang.org/api/calendar/v3" 10 | ) 11 | 12 | func deleteCalenderEvent(cID string, cevent *ProformaEvent) { 13 | err := cal_srv.Events.Delete(cID, cevent.CalID).Do() 14 | if err != nil { 15 | logrus.Errorf("Unable to create event. %v", err) 16 | } 17 | } 18 | 19 | func insertCalenderEvent(event *ProformaEvent, proforma *Proforma, loc *time.Location, time_zone string, cID string) { 20 | cevent := &calendar.Event{ 21 | Summary: fmt.Sprintf("%s - %s, %s", event.Name, proforma.Profile, proforma.CompanyName), 22 | Location: event.Venue, 23 | Description: fmt.Sprintf( 24 | "%s of profile %s - %s has been scheduled from %s to %s\nhttps://placement.iitk.ac.in/student/rc/%d/events/%d", 25 | event.Name, proforma.Profile, proforma.CompanyName, 26 | time.UnixMilli(int64(event.StartTime)).In(loc).Format("2006-01-02 15:04"), 27 | time.UnixMilli(int64(event.EndTime)).In(loc).Format("2006-01-02 15:04"), 28 | proforma.RecruitmentCycleID, event.ID), 29 | Start: &calendar.EventDateTime{ 30 | DateTime: time.UnixMilli(int64(event.StartTime)).In(loc).Format(time.RFC3339), 31 | TimeZone: time_zone, 32 | }, 33 | End: &calendar.EventDateTime{ 34 | DateTime: time.UnixMilli(int64(event.EndTime)).In(loc).Format(time.RFC3339), 35 | TimeZone: time_zone, 36 | }, 37 | } 38 | 39 | if event.CalID == "" { 40 | cevent, err := cal_srv.Events.Insert(cID, cevent).Do() 41 | if err != nil { 42 | logrus.Errorf("Unable to create event. %v", err) 43 | } 44 | 45 | event.CalID = cevent.Id 46 | err = updateEventCalID(event) 47 | if err != nil { 48 | logrus.Errorf("Unable to update event. %v", err) 49 | } 50 | } 51 | 52 | _, err := cal_srv.Events.Update(cID, event.CalID, cevent).Do() 53 | if err != nil { 54 | logrus.Errorf("Unable to update event. %v", err) 55 | } 56 | } 57 | 58 | // func insertCalenderApplicationDeadline(proforma *Proforma, event *ProformaEvent) { 59 | // time_zone := "Asia/Kolkata" 60 | // loc, _ := time.LoadLocation(time_zone) 61 | 62 | // cevent := &calendar.Event{ 63 | // Summary: fmt.Sprintf("Application Deadline: %s - %s", proforma.Profile, proforma.CompanyName), 64 | // Location: "Recruitment Automation System", 65 | // Description: fmt.Sprintf( 66 | // "A new opening has been created for the profile of %s in the company %s. Application is due %s\nhttps://placement.iitk.ac.in/student/rc/%d/proforma/%d", 67 | // proforma.Profile, proforma.CompanyName, 68 | // time.UnixMilli(int64(proforma.Deadline)).In(loc).Format("2006-01-02 15:04"), 69 | // proforma.RecruitmentCycleID, proforma.ID), 70 | // Start: &calendar.EventDateTime{ 71 | // DateTime: time.UnixMilli(int64(proforma.Deadline)).In(loc).Format(time.RFC3339), 72 | // TimeZone: time_zone, 73 | // }, 74 | // End: &calendar.EventDateTime{ 75 | // DateTime: time.UnixMilli(int64(proforma.Deadline)).In(loc).Format(time.RFC3339), 76 | // TimeZone: time_zone, 77 | // }, 78 | // } 79 | 80 | // cID := getCalenderID(proforma.RecruitmentCycleID) 81 | // if cID == "" { 82 | // logrus.Errorf("No Calendar ID found for RC ID %d", proforma.RecruitmentCycleID) 83 | // return 84 | // } 85 | // if event.CalID == "" { 86 | // insertedEvent, err := cal_srv.Events.Insert(cID, cevent).Do() 87 | // if err != nil { 88 | // logrus.Errorf("Unable to create event. %v", err) 89 | // return 90 | // } 91 | // if insertedEvent == nil { 92 | // logrus.Error("Google Calendar API returned nil event") 93 | // return 94 | // } 95 | 96 | // event.CalID = insertedEvent.Id 97 | // err = updateEventCalID(event) 98 | // if err != nil { 99 | // logrus.Errorf("Unable to update event. %v", err) 100 | // } 101 | // } 102 | 103 | // _, err := cal_srv.Events.Update(cID, event.CalID, cevent).Do() 104 | // if err != nil { 105 | // logrus.Errorf("Unable to update event. %v", err) 106 | // } 107 | // } 108 | 109 | func getCalenderID(rid uint) (cID string) { 110 | cID = viper.GetString(fmt.Sprintf("CALENDAR.CID%d", rid)) 111 | 112 | return 113 | } 114 | -------------------------------------------------------------------------------- /application/company.proforma.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | "github.com/spo-iitk/ras-backend/rc" 10 | "github.com/spo-iitk/ras-backend/util" 11 | ) 12 | 13 | func getProformaForCompanyHandler(ctx *gin.Context) { 14 | cid := getCompanyRCID(ctx) 15 | if cid == 0 { 16 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "could not get company rcid"}) 17 | return 18 | } 19 | 20 | var jps []Proforma 21 | 22 | err := fetchProformasByCompanyForCompany(ctx, cid, &jps) 23 | if err != nil { 24 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 25 | return 26 | } 27 | 28 | ctx.JSON(http.StatusOK, jps) 29 | } 30 | 31 | func postProformaByCompanyHandler(ctx *gin.Context) { 32 | cid := getCompanyRCID(ctx) 33 | if cid == 0 { 34 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "could not get company rcid"}) 35 | return 36 | } 37 | 38 | var jp Proforma 39 | err := ctx.ShouldBindJSON(&jp) 40 | if err != nil { 41 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 42 | return 43 | } 44 | 45 | var companyRC rc.CompanyRecruitmentCycle 46 | err = rc.FetchCompany(ctx, cid, &companyRC) 47 | if err != nil { 48 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 49 | return 50 | } 51 | 52 | jp.CompanyRecruitmentCycleID = cid 53 | jp.CompanyID = companyRC.CompanyID 54 | jp.RecruitmentCycleID = companyRC.RecruitmentCycleID 55 | jp.CompanyName = companyRC.CompanyName 56 | 57 | err = createProforma(ctx, &jp) 58 | if err != nil { 59 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 60 | return 61 | } 62 | 63 | user := middleware.GetUserID(ctx) 64 | 65 | logrus.Infof("%v created a proforma with id %d", user, jp.ID) 66 | ctx.JSON(http.StatusOK, gin.H{"pid": jp.ID}) 67 | } 68 | 69 | func putProformaByCompanyHandler(ctx *gin.Context) { 70 | cid := getCompanyRCID(ctx) 71 | if cid == 0 { 72 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "could not get company rcid"}) 73 | return 74 | } 75 | 76 | var jp Proforma 77 | err := ctx.ShouldBindJSON(&jp) 78 | if err != nil { 79 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 80 | return 81 | } 82 | 83 | jp.CompanyRecruitmentCycleID = cid 84 | 85 | ok, err := updateProformaForCompany(ctx, &jp) 86 | if err != nil { 87 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 88 | return 89 | } 90 | 91 | if !ok { 92 | ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "proforma not found or unauthorized"}) 93 | return 94 | } 95 | 96 | ctx.JSON(http.StatusOK, gin.H{"status": "edited proforma"}) 97 | } 98 | 99 | func deleteProformaByCompanyHandler(ctx *gin.Context) { 100 | pid, err := util.ParseUint(ctx.Param("pid")) 101 | if err != nil { 102 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 103 | return 104 | } 105 | 106 | cid := getCompanyRCID(ctx) 107 | if cid == 0 { 108 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "could not get company rcid"}) 109 | return 110 | } 111 | 112 | ok, err := deleteProformaByCompany(ctx, pid, cid) 113 | if err != nil { 114 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 115 | return 116 | } 117 | 118 | if !ok { 119 | ctx.AbortWithStatusJSON(402, gin.H{"error": "proforma not found or unauthorized"}) 120 | return 121 | } 122 | 123 | ctx.JSON(http.StatusOK, gin.H{"status": "deleted proforma"}) 124 | } 125 | 126 | func getProformaHandlerForCompany(ctx *gin.Context) { 127 | pid, err := util.ParseUint(ctx.Param("pid")) 128 | if err != nil { 129 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 130 | return 131 | } 132 | 133 | cid := getCompanyRCID(ctx) 134 | if cid == 0 { 135 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "could not get company rcid"}) 136 | return 137 | } 138 | 139 | var jp Proforma 140 | err = fetchProformaForCompany(ctx, pid, cid, &jp) 141 | if err != nil { 142 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 143 | return 144 | } 145 | 146 | ctx.JSON(http.StatusOK, jp) 147 | } 148 | -------------------------------------------------------------------------------- /application/admin.questions.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spo-iitk/ras-backend/middleware" 9 | "github.com/spo-iitk/ras-backend/util" 10 | ) 11 | 12 | func getQuestionsByProformaHandler(ctx *gin.Context) { 13 | pid, err := util.ParseUint(ctx.Param("pid")) 14 | if err != nil { 15 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 16 | return 17 | } 18 | 19 | var questions []ApplicationQuestion 20 | err = fetchProformaQuestion(ctx, pid, &questions) 21 | if err != nil { 22 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 23 | return 24 | } 25 | 26 | ctx.JSON(http.StatusOK, questions) 27 | } 28 | 29 | func postQuestionHandler(ctx *gin.Context) { 30 | var question ApplicationQuestion 31 | 32 | err := ctx.ShouldBindJSON(&question) 33 | if err != nil { 34 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 35 | return 36 | } 37 | 38 | if question.Question == "" { 39 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "question is required"}) 40 | return 41 | } 42 | 43 | pid, err := util.ParseUint(ctx.Param("pid")) 44 | if err != nil { 45 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 46 | return 47 | } 48 | 49 | question.ProformaID = pid 50 | 51 | if question.Type == "" { 52 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "type is required"}) 53 | return 54 | } 55 | 56 | err = createProformaQuestion(ctx, &question) 57 | if err != nil { 58 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 59 | return 60 | } 61 | 62 | user := middleware.GetUserID(ctx) 63 | 64 | logrus.Infof("%v created a proforma question with id %d", user, question.ID) 65 | ctx.JSON(http.StatusOK, gin.H{"qid": question.ID}) 66 | } 67 | 68 | func putQuestionHandler(ctx *gin.Context) { 69 | var question ApplicationQuestion 70 | 71 | err := ctx.ShouldBindJSON(&question) 72 | if err != nil { 73 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 74 | return 75 | } 76 | 77 | if question.ID == 0 { 78 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "id is required"}) 79 | return 80 | } 81 | 82 | err = updateProformaQuestion(ctx, &question) 83 | if err != nil { 84 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 85 | return 86 | } 87 | 88 | user := middleware.GetUserID(ctx) 89 | 90 | logrus.Infof("%v updated a proforma question with id %d", user, question.ID) 91 | 92 | ctx.JSON(http.StatusOK, gin.H{"status": "updated question successfully"}) 93 | } 94 | 95 | func deleteQuestionHandler(ctx *gin.Context) { 96 | qid, err := util.ParseUint(ctx.Param("qid")) 97 | if err != nil { 98 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 99 | return 100 | } 101 | 102 | err = deleteProformaQuestion(ctx, qid) 103 | if err != nil { 104 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 105 | return 106 | } 107 | 108 | user := middleware.GetUserID(ctx) 109 | 110 | logrus.Infof("%v deleted a proforma question with id %d", user, qid) 111 | 112 | ctx.JSON(http.StatusOK, gin.H{"status": "deleted question successfully"}) 113 | } 114 | 115 | func getAnswersForProforma(ctx *gin.Context, pid uint) map[uint](map[uint]string) { 116 | var questions []ApplicationQuestion 117 | 118 | err := fetchProformaQuestion(ctx, pid, &questions) 119 | 120 | if err != nil { 121 | ctx.AbortWithStatusJSON(http.StatusExpectationFailed, gin.H{"error": err.Error()}) 122 | } 123 | 124 | var questionID []uint 125 | for _, ques := range questions { 126 | questionID = append(questionID, ques.ID) 127 | } 128 | 129 | var answers []ApplicationQuestionAnswer 130 | 131 | err = fetchAllAnswers(ctx, pid, questionID, &answers) 132 | if err != nil { 133 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 134 | } 135 | 136 | var answers_map = make(map[uint](map[uint]string)) 137 | 138 | for _, ans := range answers { 139 | qid := ans.ApplicationQuestionID 140 | sid := ans.StudentRecruitmentCycleID 141 | 142 | if answers_map[sid] == nil { 143 | answers_map[sid] = make(map[uint]string) 144 | } 145 | 146 | answers_map[sid][qid] = ans.Answer 147 | } 148 | return answers_map 149 | } 150 | -------------------------------------------------------------------------------- /application/company.events.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/spo-iitk/ras-backend/util" 9 | ) 10 | 11 | func postEventByCompanyHandler(ctx *gin.Context) { 12 | var event ProformaEvent 13 | err := ctx.ShouldBindJSON(&event) 14 | if err != nil { 15 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 16 | return 17 | } 18 | 19 | cid := getCompanyRCID(ctx) 20 | if cid == 0 { 21 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "could not get company rcid"}) 22 | return 23 | } 24 | 25 | var jp Proforma 26 | err = fetchProforma(ctx, event.ProformaID, &jp) 27 | if err != nil { 28 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 29 | return 30 | } 31 | 32 | if jp.CompanyRecruitmentCycleID != cid { 33 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "company not authorized"}) 34 | return 35 | } 36 | 37 | err = createEvent(ctx, &event) 38 | if err != nil { 39 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 40 | return 41 | } 42 | 43 | ctx.JSON(http.StatusOK, gin.H{"status": "event created with id " + fmt.Sprint(event.ID)}) 44 | } 45 | 46 | func putEventByCompanyHandler(ctx *gin.Context) { 47 | var event ProformaEvent 48 | err := ctx.ShouldBindJSON(&event) 49 | if err != nil { 50 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 51 | return 52 | } 53 | 54 | if event.ID == 0 { 55 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "id is required"}) 56 | return 57 | } 58 | 59 | var curr_event ProformaEvent 60 | err = fetchEvent(ctx, event.ID, &curr_event) 61 | if err != nil { 62 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 63 | return 64 | } 65 | 66 | cid := getCompanyRCID(ctx) 67 | if cid == 0 { 68 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "could not get company rcid"}) 69 | return 70 | } 71 | 72 | var jp Proforma 73 | err = fetchProforma(ctx, curr_event.ProformaID, &jp) 74 | if err != nil { 75 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 76 | return 77 | } 78 | 79 | if jp.CompanyRecruitmentCycleID != cid { 80 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "company not authorized"}) 81 | return 82 | } 83 | 84 | err = updateEvent(ctx, &event) 85 | if err != nil { 86 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 87 | return 88 | } 89 | 90 | ctx.JSON(http.StatusOK, gin.H{"status": "event created with id " + fmt.Sprint(event.ID)}) 91 | } 92 | 93 | func deleteEventByCompanyHandler(ctx *gin.Context) { 94 | eid, err := util.ParseUint(ctx.Param("eid")) 95 | if err != nil { 96 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 97 | return 98 | } 99 | 100 | cid := getCompanyRCID(ctx) 101 | if cid == 0 { 102 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "could not get company rcid"}) 103 | return 104 | } 105 | 106 | var event ProformaEvent 107 | err = fetchEvent(ctx, eid, &event) 108 | if err != nil { 109 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 110 | return 111 | } 112 | 113 | var jp Proforma 114 | err = fetchProforma(ctx, event.ProformaID, &jp) 115 | if err != nil { 116 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 117 | return 118 | } 119 | 120 | if jp.CompanyRecruitmentCycleID != cid { 121 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "company not authorized"}) 122 | return 123 | } 124 | 125 | err = deleteEvent(ctx, eid) 126 | if err != nil { 127 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 128 | return 129 | } 130 | 131 | ctx.JSON(http.StatusOK, gin.H{"status": "deleted event with id " + fmt.Sprint(eid)}) 132 | } 133 | 134 | func getEventsByProformaForCompanyHandler(ctx *gin.Context) { 135 | pid, err := util.ParseUint(ctx.Param("pid")) 136 | if err != nil { 137 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 138 | return 139 | } 140 | 141 | var events []ProformaEvent 142 | err = fetchEventsByProforma(ctx, pid, &events) 143 | 144 | if err != nil { 145 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 146 | return 147 | } 148 | 149 | ctx.JSON(http.StatusOK, events) 150 | } 151 | -------------------------------------------------------------------------------- /rc/admin.notice.go: -------------------------------------------------------------------------------- 1 | package rc 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/spo-iitk/ras-backend/mail" 9 | "github.com/spo-iitk/ras-backend/middleware" 10 | "github.com/spo-iitk/ras-backend/util" 11 | ) 12 | 13 | func getAllNoticesHandler(ctx *gin.Context) { 14 | rid := ctx.Param("rid") 15 | var notices []Notice 16 | 17 | err := fetchAllNotices(ctx, rid, ¬ices) 18 | if err != nil { 19 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 20 | return 21 | } 22 | 23 | ctx.JSON(http.StatusOK, notices) 24 | } 25 | 26 | func postNoticeHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 27 | return func(ctx *gin.Context) { 28 | rid, err := util.ParseUint(ctx.Param("rid")) 29 | if err != nil { 30 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 31 | return 32 | } 33 | 34 | var notice Notice 35 | err = ctx.ShouldBindJSON(¬ice) 36 | if err != nil { 37 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 38 | return 39 | } 40 | 41 | err = CreateNotice(ctx, rid, ¬ice) 42 | if err != nil { 43 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 44 | return 45 | } 46 | 47 | // As a custom plugin by RAS God Harshit Raj, feel free to remove this after 2023. :P 48 | // plugins.NewNoticeNotification(mail_channel, notice.ID, notice.RecruitmentCycleID, notice.Title, notice.Description, notice.CreatedBy) 49 | 50 | ctx.JSON(http.StatusOK, gin.H{"status": "notice created"}) 51 | } 52 | } 53 | 54 | func CreateNotice(ctx *gin.Context, id uint, notice *Notice) error { 55 | notice.RecruitmentCycleID = uint(id) 56 | notice.LastReminderAt = 0 57 | notice.CreatedBy = middleware.GetUserID(ctx) 58 | return createNotice(ctx, notice) 59 | } 60 | 61 | func putNoticeHandler(ctx *gin.Context) { 62 | var editNoticeRequest Notice 63 | 64 | err := ctx.ShouldBindJSON(&editNoticeRequest) 65 | if err != nil { 66 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 67 | return 68 | } 69 | 70 | if editNoticeRequest.RecruitmentCycleID != 0 { 71 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Recruitment cycle id is not allowed"}) 72 | return 73 | } 74 | 75 | if editNoticeRequest.ID == 0 { 76 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "ID is required"}) 77 | return 78 | } 79 | 80 | err = updateNotice(ctx, &editNoticeRequest) 81 | if err != nil { 82 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 83 | return 84 | } 85 | 86 | ctx.JSON(http.StatusOK, editNoticeRequest) 87 | } 88 | 89 | func deleteNoticeHandler(ctx *gin.Context) { 90 | nid := ctx.Param("nid") 91 | 92 | err := removeNotice(ctx, nid) 93 | if err != nil { 94 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 95 | return 96 | } 97 | 98 | ctx.JSON(http.StatusOK, gin.H{"status": "status"}) 99 | } 100 | 101 | func postReminderHandler(mail_channel chan mail.Mail) gin.HandlerFunc { 102 | return func(ctx *gin.Context) { 103 | rid, err := util.ParseUint(ctx.Param("rid")) 104 | if err != nil { 105 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 106 | return 107 | } 108 | 109 | nid := ctx.Param("nid") 110 | 111 | var notice Notice 112 | err = fetchNotice(ctx, nid, ¬ice) 113 | if err != nil { 114 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 115 | return 116 | } 117 | 118 | if notice.LastReminderAt > time.Now().Add(-6*time.Hour).UnixMilli() { 119 | ctx.JSON(http.StatusBadRequest, gin.H{"error": "Reminder already sent"}) 120 | return 121 | } 122 | 123 | notice.LastReminderAt = time.Now().UnixMilli() 124 | err = updateNotice(ctx, ¬ice) 125 | if err != nil { 126 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 127 | return 128 | } 129 | 130 | emails, err := fetchAllUnfrozenEmails(ctx, rid) 131 | if err != nil { 132 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 133 | return 134 | } 135 | 136 | mailBody := notice.Description 137 | if notice.Deadline > 0 { 138 | deadlineTime := time.Unix(int64(notice.Deadline)/1000, 0) 139 | deadlineStr := deadlineTime.Format("02 Jan 2006 15:04") 140 | mailBody += "\n\nDeadline: " + deadlineStr 141 | } 142 | 143 | mail_channel <- mail.GenerateMails(emails, "Notice: "+notice.Title, mailBody) 144 | 145 | ctx.JSON(http.StatusOK, gin.H{"status": "mail sent"}) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /auth/user.user_db.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/sirupsen/logrus" 9 | "github.com/spo-iitk/ras-backend/constants" 10 | "github.com/spo-iitk/ras-backend/middleware" 11 | ) 12 | 13 | type UserDetails struct { 14 | UserID uint `json:"user_id" binding:"required"` 15 | Password string `json:"password" binding:"required"` 16 | RoleID constants.Role `json:"role_id" binding:"required"` // student role by default 17 | Name string `json:"name" binding:"required"` 18 | IsActive bool `json:"is_active" binding:"required"` 19 | LastLogin uint `json:"last_login" binding:"required"` 20 | RefreshToken string `json:"refresh_token" binding:"required"` 21 | } 22 | 23 | type UpdateRoleRequest struct { 24 | UserID uint `json:"user_id" binding:"required"` 25 | NewRoleID constants.Role `json:"new_role_id" binding:"required"` 26 | } 27 | 28 | func getAllAdminDetailsHandler(ctx *gin.Context) { 29 | var users []User 30 | 31 | middleware.Authenticator()(ctx) 32 | middleware.EnsureAdmin()(ctx) 33 | if middleware.GetUserID(ctx) == "" { 34 | return 35 | } 36 | 37 | if middleware.GetRoleID(ctx) < 100 { 38 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Only admin can access this page"}) 39 | return 40 | } 41 | err := fetchAllAdminDetails(ctx, &users) 42 | 43 | if err != nil { 44 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 45 | } 46 | ctx.JSON(http.StatusOK, gin.H{"users": users}) 47 | } 48 | func getAdminDetailsHandler(ctx *gin.Context) { 49 | var user User 50 | 51 | middleware.Authenticator()(ctx) 52 | middleware.EnsureAdmin()(ctx) 53 | if middleware.GetUserID(ctx) == "" { 54 | return 55 | } 56 | 57 | err := fetchAdminDetailsById(ctx, &user, ctx.Param("userID")) 58 | 59 | if err != nil { 60 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 61 | } 62 | ctx.JSON(http.StatusOK, user) 63 | } 64 | func updateUserRole(ctx *gin.Context) { 65 | 66 | var updateReq UpdateRoleRequest 67 | 68 | if err := ctx.ShouldBindJSON(&updateReq); err != nil { 69 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 70 | return 71 | } 72 | 73 | var currentRoleID constants.Role 74 | currentRoleID, err := getUserRole(ctx, updateReq.UserID) 75 | 76 | if err != nil { 77 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 78 | } 79 | 80 | middleware.Authenticator()(ctx) 81 | middleware.EnsureAdmin()(ctx) 82 | if middleware.GetUserID(ctx) == "" { 83 | return 84 | } 85 | var userId = middleware.GetUserID(ctx) 86 | 87 | _, userRole, _, err := getPasswordAndRole(ctx, userId) 88 | 89 | if err != nil { 90 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 91 | } 92 | 93 | if userRole > currentRoleID || userRole > updateReq.NewRoleID || userRole > 101 { 94 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized to update this user's role"}) 95 | return 96 | } 97 | 98 | err = updateRoleByAdmin(ctx, updateReq.UserID, updateReq.NewRoleID) 99 | if err != nil { 100 | ctx.AbortWithStatusJSON(http.StatusBadGateway, gin.H{"error": err.Error()}) 101 | } 102 | 103 | logrus.Infof("User %v role changed from %v to %v - Action taken by user with id %v", updateReq.UserID, currentRoleID, updateReq.NewRoleID, userId) 104 | ctx.JSON(http.StatusOK, gin.H{"message": "User role updated successfully"}) 105 | } 106 | 107 | func updateUserActiveStatus(ctx *gin.Context) { 108 | requestedUserId, err := strconv.ParseUint(ctx.Param("userID"), 10, 16) 109 | 110 | if err != nil { 111 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 112 | return 113 | } 114 | 115 | middleware.Authenticator()(ctx) 116 | middleware.EnsureAdmin()(ctx) 117 | 118 | userId := middleware.GetUserID(ctx) 119 | roleId := middleware.GetRoleID(ctx) 120 | 121 | var requestedUserRoleID constants.Role 122 | requestedUserRoleID, err = getUserRole(ctx, uint(requestedUserId)) 123 | if err != nil { 124 | ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 125 | return 126 | } 127 | 128 | if roleId > requestedUserRoleID && roleId > 101 { 129 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized to update this user's activity status"}) 130 | return 131 | } 132 | 133 | active, err := toggleActive(ctx, uint(requestedUserId)) 134 | if err != nil { 135 | ctx.AbortWithStatusJSON(http.StatusBadGateway, gin.H{"error": err.Error()}) 136 | return 137 | } 138 | 139 | logrus.Infof("User %v active status set to %v - Action taken by user with id %v", requestedUserId, active, userId) 140 | ctx.JSON(http.StatusOK, gin.H{"message": "User status updated successfully"}) 141 | 142 | } 143 | --------------------------------------------------------------------------------