From 50577c51bcba888bc94fc2ed4d9d3dba0fd3fa19 Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 18:05:03 +0530 Subject: [PATCH 01/14] Configure containerization and deployment settings --- Dockerfile | 15 +++++++++++ app/Dockerfile | 14 ++++++++++ app/nginx.conf | 20 +++++++++++++++ config/db.go | 5 ++-- docker-compose.yml | 51 +++++++++++++++++++++++++++++++++++++ handlers/admin_auth.go | 2 +- handlers/admin_status.go | 10 +++++--- handlers/auth.go | 2 +- handlers/centrehead_auth.go | 2 +- handlers/centrehead_post.go | 6 ++--- handlers/faculty_auth.go | 2 +- handlers/faculty_post.go | 6 ++--- handlers/warden_auth.go | 2 +- handlers/warden_post.go | 6 ++--- helpers/env.go | 8 ++++++ main.go | 11 ++++---- services/email.go | 6 +++-- 17 files changed, 141 insertions(+), 27 deletions(-) create mode 100644 Dockerfile create mode 100644 app/Dockerfile create mode 100644 app/nginx.conf create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..814c1d1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# Stage 1: Build the Go application +FROM golang:1.26-alpine AS builder +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -o main . + +# Stage 2: Final minimal image +FROM alpine:latest +RUN apk --no-cache add ca-certificates +WORKDIR /root/ +COPY --from=builder /app/main . +EXPOSE 8080 +CMD ["./main"] diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..81e9f44 --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,14 @@ +# Stage 1: Build the React application +FROM node:20-alpine AS builder +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY . . +RUN npm run build + +# Stage 2: Serve the static files using Nginx +FROM nginx:alpine +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/app/nginx.conf b/app/nginx.conf new file mode 100644 index 0000000..02a947b --- /dev/null +++ b/app/nginx.conf @@ -0,0 +1,20 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + # Proxy API requests to the Go backend + location /api/ { + proxy_pass http://backend:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +} diff --git a/config/db.go b/config/db.go index 823d356..2e24913 100644 --- a/config/db.go +++ b/config/db.go @@ -17,9 +17,10 @@ func ConnectDB() { DB_USER := helpers.GetEnv("DB_USER") DB_NAME := helpers.GetEnv("DB_NAME") DB_PASS := helpers.GetEnv("DB_PASS") - // DB_PORT := helpers.GetEnv("DB_PORT") // will see during deployment + DB_HOST := helpers.GetEnvWithDefault("DB_HOST", "localhost") + DB_PORT := helpers.GetEnvWithDefault("DB_PORT", "5432") - dsn := fmt.Sprintf("host=localhost user=%s password=%s dbname=%s port=5432 sslmode=disable", DB_USER, DB_PASS, DB_NAME) + dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT) db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..256fe2d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,51 @@ +version: '3.8' + +services: + db: + image: postgres:15-alpine + container_name: cms-db + restart: always + environment: + POSTGRES_USER: ${DB_USER:-postgres} + POSTGRES_PASSWORD: ${DB_PASS:-postgres} + POSTGRES_DB: ${DB_NAME:-cms} + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + backend: + build: + context: . + dockerfile: Dockerfile + container_name: cms-backend + restart: always + depends_on: + - db + environment: + DB_HOST: db + DB_PORT: 5432 + DB_USER: ${DB_USER:-postgres} + DB_PASS: ${DB_PASS:-postgres} + DB_NAME: ${DB_NAME:-cms} + JWT_SECRET: ${JWT_SECRET} + APP_PASSWORD: ${APP_PASSWORD} + SENDER_EMAIL: ${SENDER_EMAIL} + FRONTEND_URL: ${FRONTEND_URL:-http://localhost} + COOKIE_DOMAIN: ${COOKIE_DOMAIN:-} + ports: + - "8080:8080" + + frontend: + build: + context: ./app + dockerfile: Dockerfile + container_name: cms-frontend + restart: always + ports: + - "80:80" + depends_on: + - backend + +volumes: + postgres_data: diff --git a/handlers/admin_auth.go b/handlers/admin_auth.go index 2f47b2c..5036a1b 100644 --- a/handlers/admin_auth.go +++ b/handlers/admin_auth.go @@ -77,7 +77,7 @@ func (h *AdminHandler) AdminLogin (c *gin.Context) { token, 30 * 24 * 60 * 60, "/", - "localhost", + helpers.GetEnvWithDefault("COOKIE_DOMAIN", "localhost"), false, true, ) diff --git a/handlers/admin_status.go b/handlers/admin_status.go index 50c151c..a4343ed 100644 --- a/handlers/admin_status.go +++ b/handlers/admin_status.go @@ -15,6 +15,7 @@ import ( "strings" "time" + "github.com/ayush00git/cms-web/helpers" "github.com/ayush00git/cms-web/middleware" "github.com/ayush00git/cms-web/models" "github.com/ayush00git/cms-web/services" @@ -90,7 +91,8 @@ func (h *AdminHandler) AdminFacultyPostStatus(c *gin.Context) { } // create the postURL - postURL := fmt.Sprintf(`http://localhost:5173/admin/posts/%s/%d`, "faculty", post.ID) + frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173") + postURL := fmt.Sprintf(`%s/admin/posts/%s/%d`, frontendURL, "faculty", post.ID) switch post.Status { // ** Posts with status type mentioned PendingXEN ** @@ -436,7 +438,8 @@ func (h *AdminHandler) AdminWardenPostStatus(c *gin.Context) { } // create the postURL - postURL := fmt.Sprintf(`http://localhost:5173/admin/posts/%s/%d`, "warden", post.ID) + frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173") + postURL := fmt.Sprintf(`%s/admin/posts/%s/%d`, frontendURL, "warden", post.ID) switch post.Status { // ** Posts with status type mentioned PendingXEN ** @@ -782,7 +785,8 @@ func (h *AdminHandler) AdminCentreheadPostStatus(c *gin.Context) { } // create the postURL - postURL := fmt.Sprintf(`http://localhost:5173/admin/posts/%s/%d`, "centrehead", post.ID) + frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173") + postURL := fmt.Sprintf(`%s/admin/posts/%s/%d`, frontendURL, "centrehead", post.ID) switch post.Status { // ** Posts with status type mentioned PendingXEN ** diff --git a/handlers/auth.go b/handlers/auth.go index de01b49..5035556 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -16,7 +16,7 @@ func (h *AuthHandler) Logout (c *gin.Context) { " ", -1, "/", - "localhost", + helpers.GetEnvWithDefault("COOKIE_DOMAIN", "localhost"), false, true, ) diff --git a/handlers/centrehead_auth.go b/handlers/centrehead_auth.go index 8ee37e8..b4b7f7e 100644 --- a/handlers/centrehead_auth.go +++ b/handlers/centrehead_auth.go @@ -115,7 +115,7 @@ func (h *AuthHandler) CentreheadLogin(c *gin.Context) { token, 30 * 24 * 60 * 60, "/", - "localhost", + helpers.GetEnvWithDefault("COOKIE_DOMAIN", "localhost"), false, true, ) diff --git a/handlers/centrehead_post.go b/handlers/centrehead_post.go index 64a6c97..1a65e89 100644 --- a/handlers/centrehead_post.go +++ b/handlers/centrehead_post.go @@ -7,6 +7,7 @@ import ( "strconv" "time" + "github.com/ayush00git/cms-web/helpers" "github.com/ayush00git/cms-web/middleware" "github.com/ayush00git/cms-web/models" "github.com/ayush00git/cms-web/services" @@ -80,9 +81,8 @@ func (h *PostHandler) CentreheadPost(c *gin.Context) { return } - // send the mail to the corresponding xen - // } /> - postURL := fmt.Sprintf(`http://localhost:5173/admin/posts/%s/%d`, head.Role, post.ID) + frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173") + postURL := fmt.Sprintf(`%s/admin/posts/%s/%d`, frontendURL, head.Role, post.ID) go func() { var position models.PositionType if post.TypeOfPost == "Civil" { diff --git a/handlers/faculty_auth.go b/handlers/faculty_auth.go index 83e508c..282d05c 100644 --- a/handlers/faculty_auth.go +++ b/handlers/faculty_auth.go @@ -137,7 +137,7 @@ func (h *AuthHandler) FacultyLogin (c *gin.Context) { token, 30 * 24 * 60 * 60, // 30 days "/", - "localhost", + helpers.GetEnvWithDefault("COOKIE_DOMAIN", "localhost"), false, // set to true during deployment (secure bool) true, // set to false during deployment (httpOnly bool) ) diff --git a/handlers/faculty_post.go b/handlers/faculty_post.go index af99f67..c019890 100644 --- a/handlers/faculty_post.go +++ b/handlers/faculty_post.go @@ -7,6 +7,7 @@ import ( "strconv" "time" + "github.com/ayush00git/cms-web/helpers" "github.com/ayush00git/cms-web/middleware" "github.com/ayush00git/cms-web/models" "github.com/ayush00git/cms-web/services" @@ -87,9 +88,8 @@ func (h *PostHandler) FacultyPost(c *gin.Context) { return } - // send the mail to the corresponding xen - // } /> - postURL := fmt.Sprintf(`http://localhost:5173/admin/posts/%s/%d`, faculty.Role, post.ID) + frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173") + postURL := fmt.Sprintf(`%s/admin/posts/%s/%d`, frontendURL, faculty.Role, post.ID) go func() { var position models.PositionType if post.TypeOfPost == "Civil" { diff --git a/handlers/warden_auth.go b/handlers/warden_auth.go index 385e735..9875c7e 100644 --- a/handlers/warden_auth.go +++ b/handlers/warden_auth.go @@ -115,7 +115,7 @@ func (h *AuthHandler) WardenLogin (c *gin.Context) { token, 30 * 24 * 60 * 60, "/", - "localhost", + helpers.GetEnvWithDefault("COOKIE_DOMAIN", "localhost"), false, true, ) diff --git a/handlers/warden_post.go b/handlers/warden_post.go index 8391629..cfc298b 100644 --- a/handlers/warden_post.go +++ b/handlers/warden_post.go @@ -7,6 +7,7 @@ import ( "strconv" "time" + "github.com/ayush00git/cms-web/helpers" "github.com/ayush00git/cms-web/middleware" "github.com/ayush00git/cms-web/models" "github.com/ayush00git/cms-web/services" @@ -84,9 +85,8 @@ func (h *PostHandler) WardenPost(c *gin.Context) { return } - // send the mail to the corresponding xen - // } /> - postURL := fmt.Sprintf(`http://localhost:5173/admin/posts/%s/%d`, warden.Role, post.ID) + frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173") + postURL := fmt.Sprintf(`%s/admin/posts/%s/%d`, frontendURL, warden.Role, post.ID) go func() { var position models.PositionType if post.TypeOfPost == "Civil" { diff --git a/helpers/env.go b/helpers/env.go index 2b82ec8..0cf2976 100644 --- a/helpers/env.go +++ b/helpers/env.go @@ -12,3 +12,11 @@ func GetEnv(target string) string { } return value } + +func GetEnvWithDefault(target, defaultValue string) string { + value := os.Getenv(target) + if value == "" { + return defaultValue + } + return value +} diff --git a/main.go b/main.go index 0d99070..df73d5b 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,10 @@ package main import ( "fmt" - "log" "github.com/ayush00git/cms-web/config" "github.com/ayush00git/cms-web/handlers" + "github.com/ayush00git/cms-web/helpers" "github.com/ayush00git/cms-web/routes" "github.com/gin-contrib/cors" @@ -14,10 +14,8 @@ import ( ) func main() { - err := godotenv.Load() - if err != nil { - log.Fatal("Error while loading the environment variables") - } + // Load .env file if it exists, ignore error if missing (e.g. in docker containers) + _ = godotenv.Load() // db connection config.ConnectDB() @@ -26,7 +24,8 @@ func main() { // CORS policy and config corsConfig := cors.DefaultConfig() - corsConfig.AllowOrigins = []string{"http://localhost:5173"} + frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173") + corsConfig.AllowOrigins = []string{frontendURL} corsConfig.AllowCredentials = true r.Use(cors.New(corsConfig)) diff --git a/services/email.go b/services/email.go index 7a45f09..a24037c 100644 --- a/services/email.go +++ b/services/email.go @@ -42,7 +42,8 @@ func SendVerificationMail(userId uint, email, role string) (error) { } // create the verification url - verificationURL := fmt.Sprintf(`http://localhost:5173/account/verify?token=%s`, token) + frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173") + verificationURL := fmt.Sprintf(`%s/account/verify?token=%s`, frontendURL, token) // send the email mail := fmt.Sprintf(` @@ -79,7 +80,8 @@ func SendPasswordResetMail(userID uint, email, role string) error { if err != nil { return err } - resetURL := fmt.Sprintf(`http://localhost:5173/account/reset-password?user=%s`, token) + frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173") + resetURL := fmt.Sprintf(`%s/account/reset-password?user=%s`, frontendURL, token) // send the email mail := fmt.Sprintf(` From 24b19e74d468d98dbc52186cd27d094477b6e685 Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 18:28:01 +0530 Subject: [PATCH 02/14] Update docker-compose exposed host ports to avoid server conflicts --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 256fe2d..1f6b89d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: POSTGRES_PASSWORD: ${DB_PASS:-postgres} POSTGRES_DB: ${DB_NAME:-cms} ports: - - "5432:5432" + - "5433:5432" volumes: - postgres_data:/var/lib/postgresql/data @@ -34,7 +34,7 @@ services: FRONTEND_URL: ${FRONTEND_URL:-http://localhost} COOKIE_DOMAIN: ${COOKIE_DOMAIN:-} ports: - - "8080:8080" + - "8082:8080" frontend: build: @@ -43,7 +43,7 @@ services: container_name: cms-frontend restart: always ports: - - "80:80" + - "8083:80" depends_on: - backend From 5f93c57c70132fbbc53b87610ff3f374e428e745 Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 18:37:00 +0530 Subject: [PATCH 03/14] Add auto-seeding for default admin users --- config/db.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/config/db.go b/config/db.go index 2e24913..0c414a7 100644 --- a/config/db.go +++ b/config/db.go @@ -3,10 +3,12 @@ package config import ( "fmt" "log" + "time" "github.com/ayush00git/cms-web/helpers" "github.com/ayush00git/cms-web/models" + "golang.org/x/crypto/bcrypt" "gorm.io/driver/postgres" "gorm.io/gorm" ) @@ -40,5 +42,37 @@ func ConnectDB() { &models.Comment{}, ) + seedAdmins(DB) + log.Println("Database connected") +} + +func seedAdmins(db *gorm.DB) { + var count int64 + db.Model(&models.Admin{}).Count(&count) + if count == 0 { + log.Println("Seeding default admin users...") + admins := []models.Admin{ + {Email: "xen_civil@nith.ac.in", Position: models.TypeXENCivil, IsVerified: true}, + {Email: "ae_civil@nith.ac.in", Position: models.TypeAECivil, IsVerified: true}, + {Email: "je_civil@nith.ac.in", Position: models.TypeJECivil, IsVerified: true}, + {Email: "xen_electrical@nith.ac.in", Position: models.TypeXENElectrical, IsVerified: true}, + {Email: "ae_electrical@nith.ac.in", Position: models.TypeAEElectrical, IsVerified: true}, + {Email: "je_electrical@nith.ac.in", Position: models.TypeJEElectrical, IsVerified: true}, + } + password := "Admin@123" + hashedPass, err := bcrypt.GenerateFromPassword([]byte(password), 10) + if err != nil { + log.Printf("Failed to hash admin seed password: %v", err) + return + } + for i := range admins { + admins[i].Password = string(hashedPass) + admins[i].CreatedAt = time.Now() + if err := db.Create(&admins[i]).Error; err != nil { + log.Printf("Failed to seed admin %s: %v", admins[i].Email, err) + } + } + log.Println("Admin users seeded successfully!") + } } \ No newline at end of file From 99d5d80015898405c91a9558824072c19b2d804b Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 18:43:39 +0530 Subject: [PATCH 04/14] Update seedUsers to seed verified Faculty, Warden, and Centrehead --- config/db.go | 92 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 12 deletions(-) diff --git a/config/db.go b/config/db.go index 0c414a7..e48907e 100644 --- a/config/db.go +++ b/config/db.go @@ -42,15 +42,23 @@ func ConnectDB() { &models.Comment{}, ) - seedAdmins(DB) + seedUsers(DB) log.Println("Database connected") } -func seedAdmins(db *gorm.DB) { - var count int64 - db.Model(&models.Admin{}).Count(&count) - if count == 0 { +func seedUsers(db *gorm.DB) { + password := "Admin@123" + hashedPass, err := bcrypt.GenerateFromPassword([]byte(password), 10) + if err != nil { + log.Printf("Failed to hash seed password: %v", err) + return + } + + // 1. Seed Admins + var adminCount int64 + db.Model(&models.Admin{}).Count(&adminCount) + if adminCount == 0 { log.Println("Seeding default admin users...") admins := []models.Admin{ {Email: "xen_civil@nith.ac.in", Position: models.TypeXENCivil, IsVerified: true}, @@ -60,12 +68,6 @@ func seedAdmins(db *gorm.DB) { {Email: "ae_electrical@nith.ac.in", Position: models.TypeAEElectrical, IsVerified: true}, {Email: "je_electrical@nith.ac.in", Position: models.TypeJEElectrical, IsVerified: true}, } - password := "Admin@123" - hashedPass, err := bcrypt.GenerateFromPassword([]byte(password), 10) - if err != nil { - log.Printf("Failed to hash admin seed password: %v", err) - return - } for i := range admins { admins[i].Password = string(hashedPass) admins[i].CreatedAt = time.Now() @@ -73,6 +75,72 @@ func seedAdmins(db *gorm.DB) { log.Printf("Failed to seed admin %s: %v", admins[i].Email, err) } } - log.Println("Admin users seeded successfully!") + log.Println("Admin users seeded.") + } + + // 2. Seed Faculty + var facultyCount int64 + db.Model(&models.Faculty{}).Count(&facultyCount) + if facultyCount == 0 { + log.Println("Seeding default faculty user...") + faculty := models.Faculty{ + Name: "Test Faculty", + Email: "faculty@nith.ac.in", + Password: string(hashedPass), + Department: models.CSE, + HouseNumber: "H-101", + Block: models.BlockA, + Type: models.Type1, + PhoneNumber: "1234567890", + IsVerified: true, + CreatedAt: time.Now(), + } + if err := db.Create(&faculty).Error; err != nil { + log.Printf("Failed to seed faculty user: %v", err) + } else { + log.Println("Faculty user seeded.") + } + } + + // 3. Seed Warden + var wardenCount int64 + db.Model(&models.Warden{}).Count(&wardenCount) + if wardenCount == 0 { + log.Println("Seeding default warden user...") + warden := models.Warden{ + Name: "Test Warden", + Email: "warden@nith.ac.in", + Password: string(hashedPass), + Hostel: models.KBH, + PhoneNumber: "1234567890", + IsVerified: true, + CreatedAt: time.Now(), + } + if err := db.Create(&warden).Error; err != nil { + log.Printf("Failed to seed warden user: %v", err) + } else { + log.Println("Warden user seeded.") + } + } + + // 4. Seed Centrehead + var centreheadCount int64 + db.Model(&models.Centrehead{}).Count(¢reheadCount) + if centreheadCount == 0 { + log.Println("Seeding default centrehead user...") + centrehead := models.Centrehead{ + Name: "Test Centrehead", + Email: "centrehead@nith.ac.in", + Password: string(hashedPass), + Building: models.ComputerCentre, + PhoneNumber: "1234567890", + IsVerified: true, + CreatedAt: time.Now(), + } + if err := db.Create(¢rehead).Error; err != nil { + log.Printf("Failed to seed centrehead user: %v", err) + } else { + log.Println("Centrehead user seeded.") + } } } \ No newline at end of file From e4a99d686cb67b3972fc90632e1b07abf2b1c393 Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 18:50:21 +0530 Subject: [PATCH 05/14] Configure deployment workflow and make seeding conditional on SEED_DB --- .github/workflows/ci.yml | 26 ---------------------- .github/workflows/deploy.yml | 43 ++++++++++++++++++++++++++++++++++++ config/db.go | 4 +++- 3 files changed, 46 insertions(+), 27 deletions(-) delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 93a727a..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: CI - -on: - pull_request: - branches: [main] - push: - branches: [main] - -jobs: - test: - name: Run tests - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - cache: true - - - name: Download dependencies - run: go mod download - - - name: Run tests - run: go test ./test/... -v -count=1 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..2df9ac7 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,43 @@ +name: Deploy to College Server + +on: + push: + branches: [main] + +jobs: + test: + name: Run tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Download dependencies + run: go mod download + + - name: Run tests + run: go test ./test/... -v -count=1 + + deploy: + name: Deploy via SSH + needs: test + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: Deploy to Server via SSH + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: 22 + script: | + cd ~/cms-webb + git pull origin main + sudo docker compose up -d --build diff --git a/config/db.go b/config/db.go index e48907e..1f71355 100644 --- a/config/db.go +++ b/config/db.go @@ -42,7 +42,9 @@ func ConnectDB() { &models.Comment{}, ) - seedUsers(DB) + if helpers.GetEnvWithDefault("SEED_DB", "false") == "true" { + seedUsers(DB) + } log.Println("Database connected") } From fa38fe81f6f5e2b74c7d4a31781d49d58358b1c1 Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 19:08:07 +0530 Subject: [PATCH 06/14] Update deployment workflow to run tests on pull requests --- .github/workflows/deploy.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2df9ac7..05614bc 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,6 +3,8 @@ name: Deploy to College Server on: push: branches: [main] + pull_request: + branches: [main] jobs: test: @@ -27,7 +29,7 @@ jobs: name: Deploy via SSH needs: test runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' + if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - name: Deploy to Server via SSH From 2a53b969a784bf56d838e3722bd142d9417fbe1d Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 19:13:33 +0530 Subject: [PATCH 07/14] Test automated CI/CD deployment pipeline --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c546102..9cdb2e3 100644 --- a/README.md +++ b/README.md @@ -26,4 +26,6 @@ the official web interface of the Estate Office of NIT Hamirpur to manage compla ### Complaint status public dashboard -Complaint status public dashboard \ No newline at end of file +Complaint status public dashboard + + \ No newline at end of file From 8c5525c251dda1e136fdd2d86fdf5e2847d478c0 Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 19:18:42 +0530 Subject: [PATCH 08/14] Switch CI/CD auth to SSH_PASSWORD --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 05614bc..d3d4238 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -37,7 +37,7 @@ jobs: with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USERNAME }} - key: ${{ secrets.SSH_PRIVATE_KEY }} + password: ${{ secrets.SSH_PASSWORD }} port: 22 script: | cd ~/cms-webb From e79ab90c02a40a83b14ead91b377f078c5799c7a Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 19:21:02 +0530 Subject: [PATCH 09/14] Test automated CI/CD deployment with password auth --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9cdb2e3..90b7405 100644 --- a/README.md +++ b/README.md @@ -28,4 +28,4 @@ the official web interface of the Estate Office of NIT Hamirpur to manage compla Complaint status public dashboard - \ No newline at end of file + \ No newline at end of file From 2fcfd8bf34c301b02ca4062069ccba2fe12f3bd0 Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 19:24:57 +0530 Subject: [PATCH 10/14] Remove sudo prefix from docker compose command in CI/CD --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d3d4238..a43d5b7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -42,4 +42,4 @@ jobs: script: | cd ~/cms-webb git pull origin main - sudo docker compose up -d --build + docker compose up -d --build From 31758142f94d0b7bc3170e9ec01a95a780376363 Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 19:27:29 +0530 Subject: [PATCH 11/14] Use sg docker -c for compose command --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a43d5b7..36f3a6f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -42,4 +42,4 @@ jobs: script: | cd ~/cms-webb git pull origin main - docker compose up -d --build + sg docker -c "docker compose up -d --build" From 7ef0048bc340331914b3689d6e556c5d376b5aa7 Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 19:28:47 +0530 Subject: [PATCH 12/14] Refactor backend to load environment from .env file directly --- docker-compose.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1f6b89d..eb42c65 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,17 +22,11 @@ services: restart: always depends_on: - db + env_file: + - .env environment: DB_HOST: db DB_PORT: 5432 - DB_USER: ${DB_USER:-postgres} - DB_PASS: ${DB_PASS:-postgres} - DB_NAME: ${DB_NAME:-cms} - JWT_SECRET: ${JWT_SECRET} - APP_PASSWORD: ${APP_PASSWORD} - SENDER_EMAIL: ${SENDER_EMAIL} - FRONTEND_URL: ${FRONTEND_URL:-http://localhost} - COOKIE_DOMAIN: ${COOKIE_DOMAIN:-} ports: - "8082:8080" From ef79563efae5f34633c67caba405f18e11405056 Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 19:32:40 +0530 Subject: [PATCH 13/14] Use sudo docker compose in deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 36f3a6f..d3d4238 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -42,4 +42,4 @@ jobs: script: | cd ~/cms-webb git pull origin main - sg docker -c "docker compose up -d --build" + sudo docker compose up -d --build From 3ac4d3024512330d62928c67681a956c754d7a64 Mon Sep 17 00:00:00 2001 From: Divyansh Jamwal Date: Thu, 25 Jun 2026 19:44:07 +0530 Subject: [PATCH 14/14] Remove seeding logic from database config for production --- config/db.go | 104 --------------------------------------------------- 1 file changed, 104 deletions(-) diff --git a/config/db.go b/config/db.go index 1f71355..2e24913 100644 --- a/config/db.go +++ b/config/db.go @@ -3,12 +3,10 @@ package config import ( "fmt" "log" - "time" "github.com/ayush00git/cms-web/helpers" "github.com/ayush00git/cms-web/models" - "golang.org/x/crypto/bcrypt" "gorm.io/driver/postgres" "gorm.io/gorm" ) @@ -42,107 +40,5 @@ func ConnectDB() { &models.Comment{}, ) - if helpers.GetEnvWithDefault("SEED_DB", "false") == "true" { - seedUsers(DB) - } - log.Println("Database connected") -} - -func seedUsers(db *gorm.DB) { - password := "Admin@123" - hashedPass, err := bcrypt.GenerateFromPassword([]byte(password), 10) - if err != nil { - log.Printf("Failed to hash seed password: %v", err) - return - } - - // 1. Seed Admins - var adminCount int64 - db.Model(&models.Admin{}).Count(&adminCount) - if adminCount == 0 { - log.Println("Seeding default admin users...") - admins := []models.Admin{ - {Email: "xen_civil@nith.ac.in", Position: models.TypeXENCivil, IsVerified: true}, - {Email: "ae_civil@nith.ac.in", Position: models.TypeAECivil, IsVerified: true}, - {Email: "je_civil@nith.ac.in", Position: models.TypeJECivil, IsVerified: true}, - {Email: "xen_electrical@nith.ac.in", Position: models.TypeXENElectrical, IsVerified: true}, - {Email: "ae_electrical@nith.ac.in", Position: models.TypeAEElectrical, IsVerified: true}, - {Email: "je_electrical@nith.ac.in", Position: models.TypeJEElectrical, IsVerified: true}, - } - for i := range admins { - admins[i].Password = string(hashedPass) - admins[i].CreatedAt = time.Now() - if err := db.Create(&admins[i]).Error; err != nil { - log.Printf("Failed to seed admin %s: %v", admins[i].Email, err) - } - } - log.Println("Admin users seeded.") - } - - // 2. Seed Faculty - var facultyCount int64 - db.Model(&models.Faculty{}).Count(&facultyCount) - if facultyCount == 0 { - log.Println("Seeding default faculty user...") - faculty := models.Faculty{ - Name: "Test Faculty", - Email: "faculty@nith.ac.in", - Password: string(hashedPass), - Department: models.CSE, - HouseNumber: "H-101", - Block: models.BlockA, - Type: models.Type1, - PhoneNumber: "1234567890", - IsVerified: true, - CreatedAt: time.Now(), - } - if err := db.Create(&faculty).Error; err != nil { - log.Printf("Failed to seed faculty user: %v", err) - } else { - log.Println("Faculty user seeded.") - } - } - - // 3. Seed Warden - var wardenCount int64 - db.Model(&models.Warden{}).Count(&wardenCount) - if wardenCount == 0 { - log.Println("Seeding default warden user...") - warden := models.Warden{ - Name: "Test Warden", - Email: "warden@nith.ac.in", - Password: string(hashedPass), - Hostel: models.KBH, - PhoneNumber: "1234567890", - IsVerified: true, - CreatedAt: time.Now(), - } - if err := db.Create(&warden).Error; err != nil { - log.Printf("Failed to seed warden user: %v", err) - } else { - log.Println("Warden user seeded.") - } - } - - // 4. Seed Centrehead - var centreheadCount int64 - db.Model(&models.Centrehead{}).Count(¢reheadCount) - if centreheadCount == 0 { - log.Println("Seeding default centrehead user...") - centrehead := models.Centrehead{ - Name: "Test Centrehead", - Email: "centrehead@nith.ac.in", - Password: string(hashedPass), - Building: models.ComputerCentre, - PhoneNumber: "1234567890", - IsVerified: true, - CreatedAt: time.Now(), - } - if err := db.Create(¢rehead).Error; err != nil { - log.Printf("Failed to seed centrehead user: %v", err) - } else { - log.Println("Centrehead user seeded.") - } - } } \ No newline at end of file