Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Ticket Management System (EVA Bharat Backend Assignment)

A lightweight, robust, and idiomatic RESTful Ticket Management System built in Go (Golang) with PostgreSQL storage and JWT authentication.

This project was built following the EVA Bharat Backend Intern Assignment specification, emphasizing clean architecture, readable and non-overengineered code, strict ownership-based authorization, and deployment readiness.

🌐 Live Production Deployment


πŸ“‹ Table of Contents


🎯 Overview

The system allows users to:

  1. Register a secure user account (passwords securely hashed with bcrypt).
  2. Authenticate to obtain a JSON Web Token (JWT).
  3. Create tickets that start in open status.
  4. View and list only the tickets created by the logged-in user.
  5. Transition a ticket's status through an enforced progression: open -> in_progress -> closed.
  6. Enforce that closed tickets can never be reopened or modified.

✨ Key Features

  • No Over-engineering: Built with clean Go standard library routing (net/http) and minimal vetted libraries.
  • Secure Password Storage: Passwords are never stored in plain text; hashed using standard bcrypt.
  • JWT Authentication: Protected endpoints require standard Authorization: Bearer <token>.
  • Strict Data Isolation: Users can never view, list, or mutate tickets owned by other users. Accessing another user's ticket returns 404 Not Found to prevent data leakage or ID enumeration.
  • Enforced State Machine: Ticket status strictly follows open -> in_progress -> closed. Reopening closed tickets is rejected with 400 Bad Request.
  • Pure Go PostgreSQL: Uses github.com/lib/pq (standard pure Go PostgreSQL driver with zero CGO), making builds seamless across Windows, macOS, and Linux Docker without C compiler dependencies.

πŸ“ Project Structure

TicketSystem/
β”œβ”€β”€ backend/            # Complete Go REST backend
β”‚   β”œβ”€β”€ auth/           # Password hashing (bcrypt) & JWT token handling
β”‚   β”‚   β”œβ”€β”€ jwt.go
β”‚   β”‚   └── password.go
β”‚   β”œβ”€β”€ database/       # PostgreSQL connection, schema migrations, and queries
β”‚   β”‚   └── db.go
β”‚   β”œβ”€β”€ handlers/       # REST API HTTP handlers
β”‚   β”‚   β”œβ”€β”€ auth.go     # Register and login handlers
β”‚   β”‚   β”œβ”€β”€ health.go   # Health check handler
β”‚   β”‚   └── tickets.go  # Ticket CRUD and status handlers
β”‚   β”œβ”€β”€ middleware/     # Auth middleware and HTTP JSON response helpers
β”‚   β”‚   └── auth.go
β”‚   β”œβ”€β”€ models/         # Domain models, status rules, and DTOs
β”‚   β”‚   β”œβ”€β”€ ticket.go
β”‚   β”‚   └── user.go
β”‚   β”œβ”€β”€ .dockerignore
β”‚   β”œβ”€β”€ .env.example    # Sample environment configuration
β”‚   β”œβ”€β”€ Dockerfile      # Multi-stage Docker build for deployment
β”‚   β”œβ”€β”€ go.mod          # Module definition
β”‚   β”œβ”€β”€ go.sum          # Checksums for dependencies
β”‚   β”œβ”€β”€ main.go         # Server bootstrap and route configuration
β”‚   └── main_test.go    # End-to-end integration and unit tests
β”œβ”€β”€ frontend/           # React frontend application
β”œβ”€β”€ .gitignore
└── README.md           # Project documentation

πŸš€ Getting Started Locally

Prerequisites

Steps to Run

  1. Navigate to the backend directory:

    cd backend
  2. Download dependencies:

    go mod download
  3. Run the server:

    go run main.go
  4. The server will start on port 8080:

    Ticket System backend listening on http://localhost:8080
    
  5. Verify the service is running:

    curl http://localhost:8080/health

🐳 Running with Docker

You can build and run the application container using Docker:

From the backend/ directory:

cd backend
docker build -t ticket-system .
docker run -p 8080:8080 ticket-system

Or from the root directory:

docker build -t ticket-system -f backend/Dockerfile backend
docker run -p 8080:8080 ticket-system

Verify:

curl http://localhost:8080/health

βš™οΈ Environment Variables

An example configuration file is provided in .env.example:

Variable Default Value Description
PORT 8080 The HTTP port the server listens on
JWT_SECRET your-secret-key-here Secret key used to sign and verify JWT tokens
DATABASE_URL your-postgres-service-url-here PostgreSQL connection string

πŸ“– API Documentation & Contract

1. Health Check

Public endpoint to check if the server is alive.

  • Method: GET
  • Endpoint: /health
  • Authentication: None (Public)
  • Response: 200 OK
    {
      "status": "ok"
    }

2. User Registration

Registers a new user account with secure password hashing.

  • Method: POST
  • Endpoint: /auth/register
  • Authentication: None (Public)
  • Request Body:
    {
      "name": "Jane Doe",
      "email": "jane@example.com",
      "password": "strongPassword123"
    }
  • Response: 201 Created
    {
      "message": "user registered successfully",
      "user": {
        "id": 1,
        "name": "Jane Doe",
        "email": "jane@example.com",
        "created_at": "2026-09-11T07:40:00Z"
      }
    }
  • Error Codes:
    • 400 Bad Request: Missing fields or password shorter than 6 characters.
    • 409 Conflict: User with the same email already exists.

3. User Login

Verifies credentials and returns a signed JWT token.

  • Method: POST
  • Endpoint: /auth/login
  • Authentication: None (Public)
  • Request Body:
    {
      "email": "jane@example.com",
      "password": "strongPassword123"
    }
  • Response: 200 OK
    {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
  • Error Codes:
    • 401 Unauthorized: Invalid email or incorrect password.

4. Create Ticket

Creates a new ticket under the authenticated user's account.

  • Method: POST
  • Endpoint: /tickets
  • Authentication: Bearer <token>
  • Request Body:
    {
      "title": "Payment Gateway Timeout",
      "description": "User reported failure during checkout payment step."
    }
  • Response: 201 Created
    {
      "id": 1,
      "title": "Payment Gateway Timeout",
      "description": "User reported failure during checkout payment step.",
      "status": "open",
      "user_id": 1,
      "created_at": "2026-09-11T07:42:00Z",
      "updated_at": "2026-09-11T07:42:00Z"
    }
  • Notes: Initial status is always automatically set to "open".

5. List My Tickets

Retrieves all tickets created by the authenticated user.

  • Method: GET
  • Endpoint: /tickets
  • Authentication: Bearer <token>
  • Response: 200 OK
    [
      {
        "id": 1,
        "title": "Payment Gateway Timeout",
        "description": "User reported failure during checkout payment step.",
        "status": "open",
        "user_id": 1,
        "created_at": "2026-09-11T07:42:00Z",
        "updated_at": "2026-09-11T07:42:00Z"
      }
    ]
  • Notes: Never returns tickets created by any other user.

6. Get Ticket by ID

Retrieves details of a single ticket by its ID.

  • Method: GET
  • Endpoint: /tickets/{id}
  • Authentication: Bearer <token>
  • Response: 200 OK
    {
      "id": 1,
      "title": "Payment Gateway Timeout",
      "description": "User reported failure during checkout payment step.",
      "status": "open",
      "user_id": 1,
      "created_at": "2026-09-11T07:42:00Z",
      "updated_at": "2026-09-11T07:42:00Z"
    }
  • Security Rule: If the ticket does not exist or belongs to another user, returns 404 Not Found.

7. Update Ticket Status

Updates the lifecycle status of a ticket owned by the user.

  • Method: PATCH
  • Endpoint: /tickets/{id}/status
  • Authentication: Bearer <token>
  • Request Body:
    {
      "status": "in_progress"
    }
  • Response: 200 OK
    {
      "id": 1,
      "title": "Payment Gateway Timeout",
      "description": "User reported failure during checkout payment step.",
      "status": "in_progress",
      "user_id": 1,
      "created_at": "2026-09-11T07:42:00Z",
      "updated_at": "2026-09-11T07:45:10Z"
    }
  • Error Codes:
    • 400 Bad Request: Invalid transition (e.g., trying to jump from open to closed, or attempting to modify a closed ticket).
    • 404 Not Found: Ticket does not exist or belongs to another user.

πŸ”„ Ticket Status Lifecycle

The ticket system enforces a strict sequential workflow:

[ open ]  ───────>  [ in_progress ]  ───────>  [ closed ]

Transition Rules:

  1. Creation: Every new ticket starts with status open.
  2. Valid Transitions:
    • open -> in_progress
    • in_progress -> closed
  3. Forbidden Transitions:
    • open -> closed (skipping intermediate step is rejected)
    • in_progress -> open (moving backward is rejected)
    • closed -> open (reopening is strictly rejected)
    • closed -> in_progress (reopening is strictly rejected)

Any attempt to execute a forbidden transition results in a 400 Bad Request status code with an informative error message.


πŸ“Έ Screenshots & Working Flow

The following screenshots demonstrate the verified end-to-end working flow tested via Thunder Client:

Server Health Check (200 OK)
     ↓
User Registration (201 Created)
     ↓
User Login & JWT Generation (200 OK)
     ↓
Bearer JWT Authentication (Header Setup)
     ↓
Create Ticket with initial status 'open' (201 Created)
     ↓
GET Ticket by ID (200 OK)
     ↓
PATCH: open β†’ in_progress (200 OK)
     ↓
PATCH: in_progress β†’ closed (200 OK)
     ↓
Attempt to reopen/update closed ticket (400 Bad Request)

1. Server Health Check (GET /health)

Verifies that the server is running and responding with status 200 OK ({"status": "ok"}).

Server Health Check


2. User Registration (POST /auth/register)

Registers a new user (ganesh, pankajad20@gmail.com) with password securely hashed via bcrypt, returning status 201 Created.

User Registration


3. User Login (POST /auth/login)

Authenticates credentials and returns a signed JSON Web Token (JWT) with status 200 OK.

User Login


4. Bearer JWT Authentication (Authorization Header)

Configures the Authorization: Bearer <token> HTTP header required to authenticate all protected ticket endpoints.

JWT Bearer Token Authentication


5. Create Ticket (POST /tickets)

Creates a new ticket under the authenticated user's account with initial status "open", returning status 201 Created.

Create Ticket


6. Get Ticket by ID (GET /tickets/{id})

Retrieves details of the created ticket (id: 7) with status 200 OK, enforcing user data ownership.

Get Ticket by ID


7. Status Transition: open β†’ in_progress (PATCH /tickets/{id}/status)

Transitions the ticket from open to in_progress using PATCH /tickets/7/status, returning status 200 OK.

Status Transition: open to in_progress


8. Status Transition: in_progress β†’ closed (PATCH /tickets/{id}/status)

Transitions the ticket from in_progress to closed using PATCH /tickets/7/status, returning status 200 OK.

Status Transition: in_progress to closed


9. Closed Ticket Cannot Be Reopened (400 Bad Request)

Enforces lifecycle immutability: attempting to update or reopen a closed ticket is strictly rejected with 400 Bad Request ({"error": "closed ticket cannot be updated or reopened"}).

Attempt to Reopen Closed Ticket Rejected


🌐 Deployed Full-Stack Application (Live on Render)

The Ticket Management System is fully deployed in production:

10. Deployed Dashboard & Ticket Management

User dashboard on the deployed frontend showing authenticated session, ticket creation form, and listed tickets with OPEN status.

Deployed Dashboard


11. Deployed Ticket Details Modal (OPEN)

Viewing ticket details on production with OPEN status badge and action button to Start Progress.

Deployed Ticket Details Modal Open


12. Deployed Status Transition (IN_PROGRESS)

Ticket status successfully transitioned to IN_PROGRESS on production, with action button to Close Ticket.

Deployed Ticket In Progress


13. Deployed Status Transition (CLOSED) & Immutability Enforcement

Ticket status moved to CLOSED on production. Further status modifications are disabled, displaying "Closed ticket cannot be reopened".

Deployed Ticket Closed


πŸ”’ Authorization & Security

  1. Passwords: Securely hashed with bcrypt using a salt generated per user.
  2. Bearer Token Authentication: Standard JWT tokens signed with HMAC-SHA256 (HS256).
  3. Context Injection: Authenticated user ID is passed safely through the request context.
  4. Data Isolation: All ticket database queries and mutations include WHERE id = ? AND user_id = ?.
  5. No Data Leakage: Accessing another user's ticket responds with 404 Not Found, preventing unauthorized users from detecting whether a ticket ID exists.

πŸ’‘ Assumptions

  1. User Identity: User emails are treated as unique, case-insensitive identifiers.
  2. Ticket Assignment & Roles: In accordance with the assignment brief, no admin role or ticket assignment flow is implemented. Every user acts as the sole manager of their own tickets.
  3. Database: PostgreSQL is used for robust, persistent relational storage across service restarts and spin-downs.
  4. Token Expiration: JWT tokens are issued with a 24-hour expiration window.

πŸ§ͺ Automated Testing

The project includes an end-to-end integration test suite verifying every functional requirement and edge case.

Run all tests:

cd backend
go test -v ./...

Note: Database-dependent tests connect using the TEST_DATABASE_URL (or DATABASE_URL) environment variable. If unset, database-dependent tests are cleanly skipped.

Tested Scenarios:

  • TestHealthCheck: Validates public access and exact {"status": "ok"} JSON format.
  • TestAuth_RegisterAndLogin: Validates registration, duplicate email rejection (409), password length checks (400), correct login with JWT (200), and invalid credentials rejection (401).
  • TestTickets_AuthRequirement: Validates missing and malformed token rejection (401).
  • TestTickets_CreateAndInitialStatus: Validates ticket creation with default status open.
  • TestTickets_OwnershipAndIsolation: Validates that users only see their own tickets and cannot view or update tickets belonging to others.
  • TestTickets_StatusTransitions: Validates strictly allowed flow (open -> in_progress -> closed) and rejections for skips and reopenings.
  • TestDatabase_DirectOperations: Directly validates database operations (user registration, duplicate handling, login lookup, ticket CRUD, isolation, and status transitions).
  • TestCORS: Validates preflight and CORS response headers for allowed origins.

☁️ Free Cloud Deployment Guide

The service includes a multi-stage Dockerfile ready to deploy on any free hosting service (Render, Railway, Fly.io, or Koyeb).

Option 1: Render.com (Recommended Free Hosting)

  1. Push your code to GitHub.
  2. Sign up / log in to Render.
  3. Create a free PostgreSQL instance on Render and copy its Internal Database URL (or External URL).
  4. Click New + -> Web Service.
  5. Connect your GitHub repository.
  6. In the settings:
    • Environment: Docker
    • Plan: Free
  7. Under Environment Variables, add:
    • DATABASE_URL: your-postgres-service-url-here (your Render PostgreSQL connection string)
    • JWT_SECRET: your-secret-key-here
  8. Click Deploy Web Service.
  9. Render will provide a public URL like https://ticket-system-xyz.onrender.com.
  10. Your public health check URL will be:
https://ticket-system-xyz.onrender.com/health

Option 2: Railway.app

  1. Push your code to GitHub.
  2. In Railway, click New Project -> Deploy from GitHub repo.
  3. Railway automatically detects the Dockerfile and deploys it.
  4. Generate a public domain in the service networking settings.

πŸ’» Frontend Application (React + Vite)

A clean, developer-focused React application is located in frontend/.

Features:

  • Dark, Professional Theme: Charcoal/dark green tones, crisp borders, and accessible typography.
  • Register & Login: Clean credential forms with human-readable error handling and JWT storage.
  • Dashboard:
    • Create Ticket: Submit tickets with automatic refresh upon creation.
    • My Tickets: Displays only the authenticated user's tickets.
    • Status Transitions: Sequential buttons (open -> in_progress -> closed) that prevent invalid state transitions.
    • Ticket Details: Detailed modal view powered by GET /tickets/{id}.
    • Automatic 401 Handling: Returns the user cleanly to the login screen if the token expires.

How to Run the Frontend:

  1. Navigate to the frontend directory:

    cd frontend
  2. Install dependencies:

    npm install
  3. Configure API URL (Optional): Copy .env.example to .env:

    cp .env.example .env

    By default, Vite proxies /health, /auth, and /tickets to http://localhost:8080 in local development without CORS issues. For production deployment, set VITE_API_URL to your backend URL (e.g. https://your-backend.onrender.com).

  4. Start the development server:

    npm run dev

    Open http://localhost:5173 in your browser.

About

Built and deployed a Ticket Management System using Go, React.js, SQLite, JWT authentication, and Docker, with secure user authentication and controlled ticket status management.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages