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..d3d4238 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,45 @@ +name: Deploy to College Server + +on: + push: + branches: [main] + pull_request: + 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.event_name == 'push' && 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 }} + password: ${{ secrets.SSH_PASSWORD }} + port: 22 + script: | + cd ~/cms-webb + git pull origin main + sudo docker compose up -d --build 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/README.md b/README.md index c546102..90b7405 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 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..eb42c65 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,45 @@ +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: + - "5433:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + backend: + build: + context: . + dockerfile: Dockerfile + container_name: cms-backend + restart: always + depends_on: + - db + env_file: + - .env + environment: + DB_HOST: db + DB_PORT: 5432 + ports: + - "8082:8080" + + frontend: + build: + context: ./app + dockerfile: Dockerfile + container_name: cms-frontend + restart: always + ports: + - "8083: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(`