├── 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 |
--------------------------------------------------------------------------------